[Bug] v0.26 policy: reduce routes sent to peers based on packetfilter #2561 #1026

Closed
opened 2025-12-29 02:27:41 +01:00 by adam · 8 comments
Owner

Originally created by @hanjo on GitHub (May 17, 2025).

Is this a support request?

  • This is not a support request

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

Hi,

v0.26 introduced this feature:
policy: reduce routes sent to peers based on packetfilter https://github.com/juanfont/headscale/pull/2561
described in this bug report: https://github.com/juanfont/headscale/issues/2365

Assume the following situation:

Node A (100.123.45.67) is a Router (part of group routers). Network 192.168.1.0/14 is connected to this Router.
Node B (100.123.45.89) is a regular Node.

Policy in place (simplified):

{
  "acls": [
    {
      "action": "accept",
      "src": [
        "192.168.1.0/24",
        "group:routers"
      ],
      "dst": [
        "*:*"
      ]
    }
  ]
}

This policy allows traffic from all routers and the network 192.168.1.0/24 to everywhere.

Before v0.26 it was possible to connect (for example ssh) directly from Node A (100.123.45.67) to Node B (100.123.45.89), and also from any machine from the 192.168.1.0/24 network to Node B (100.123.45.89). Connections from Node B to Node A or any machine on 192.168.1.0/24 was restricted though.

With the reduction of routes sent to peers based on packetfilter, the connection from the network 192.168.1.0/24 to Node B (100.123.45.89) does not work any more, since Node B has no route to send the response back to.

So while the policy in place allows said connectivity, it pratically cannot work any more.

Expected Behavior

Established and related connections should be allowed, like it was before 0.26. For this, the routes need to be known to the node.

Steps To Reproduce

Set up per above scenario.

Environment

- OS: Ubuntu 24.04.2 LTS
- Headscale version: 0.26
- Tailscale version:1.82.5

Runtime environment

  • Headscale is behind a (reverse) proxy
  • Headscale runs in a container

Debug information

Happy to provide this if really required, however I believe the description above explains it in sufficient detail.

Originally created by @hanjo on GitHub (May 17, 2025). ### Is this a support request? - [x] This is not a support request ### Is there an existing issue for this? - [x] I have searched the existing issues ### Current Behavior Hi, v0.26 introduced this feature: policy: reduce routes sent to peers based on packetfilter https://github.com/juanfont/headscale/pull/2561 described in this bug report: https://github.com/juanfont/headscale/issues/2365 Assume the following situation: Node A (100.123.45.67) is a Router (part of group routers). Network 192.168.1.0/14 is connected to this Router. Node B (100.123.45.89) is a regular Node. Policy in place (simplified): ```json { "acls": [ { "action": "accept", "src": [ "192.168.1.0/24", "group:routers" ], "dst": [ "*:*" ] } ] } ``` This policy allows traffic from all routers and the network 192.168.1.0/24 to everywhere. Before v0.26 it was possible to connect (for example ssh) directly from Node A (100.123.45.67) to Node B (100.123.45.89), and also from any machine from the 192.168.1.0/24 network to Node B (100.123.45.89). Connections from Node B to Node A or any machine on 192.168.1.0/24 was restricted though. With the reduction of routes sent to peers based on packetfilter, the connection from the network 192.168.1.0/24 to Node B (100.123.45.89) does not work any more, since Node B has no route to send the response back to. So while the policy in place allows said connectivity, it pratically cannot work any more. ### Expected Behavior Established and related connections should be allowed, like it was before 0.26. For this, the routes need to be known to the node. ### Steps To Reproduce Set up per above scenario. ### Environment ```markdown - OS: Ubuntu 24.04.2 LTS - Headscale version: 0.26 - Tailscale version:1.82.5 ``` ### Runtime environment - [x] Headscale is behind a (reverse) proxy - [x] Headscale runs in a container ### Debug information Happy to provide this if really required, however I believe the description above explains it in sufficient detail.
adam added the bugno-stale-botpolicy 📝routes labels 2025-12-29 02:27:41 +01:00
adam closed this issue 2025-12-29 02:27:41 +01:00
Author
Owner

@ArcticLampyrid commented on GitHub (Jun 28, 2025):

Try add:

    {
      "action": "accept",
      "src": [
        "*"
      ],
      "dst": [
        "192.168.1.0/24:0",
        "group:routers:0"
      ]
    }
@ArcticLampyrid commented on GitHub (Jun 28, 2025): Try add: ```json { "action": "accept", "src": [ "*" ], "dst": [ "192.168.1.0/24:0", "group:routers:0" ] } ```
Author
Owner

@hanjo commented on GitHub (Jun 28, 2025):

I cannot test if this would work, I went back to 0.25 for the time being, but even if this works, this is not what I want, since it would grant everybody access to the 192.168.1.0/24 network, which is not desired.

The goal is that devices from 192.168.1.0/24 can connect to node B, but not vice versa. This works fine in 0.25, since the node B has the route for the 192.168.1.0/24 network and the policy allows related, established cconnections, but drops new connections. In 0.26, since there is no route specific route, the device would send the traffic to default route, which obviously doesn't work.

@hanjo commented on GitHub (Jun 28, 2025): I cannot test if this would work, I went back to 0.25 for the time being, but even if this works, this is not what I want, since it would grant everybody access to the 192.168.1.0/24 network, which is not desired. The goal is that devices from 192.168.1.0/24 can connect to node B, but not vice versa. This works fine in 0.25, since the node B has the route for the 192.168.1.0/24 network and the policy allows related, established cconnections, but drops new connections. In 0.26, since there is no route specific route, the device would send the traffic to default route, which obviously doesn't work.
Author
Owner

@ArcticLampyrid commented on GitHub (Jun 28, 2025):

this is not what I want, since it would grant everybody access to the 192.168.1.0/24 network, which is not desired.

No. It blocks all ports (192.168.1.0/24:0 ends with :0), so nobody has permission to proactively establish a connection to 192.168.1.0/24. (ICMP ping may be an exception.)

@ArcticLampyrid commented on GitHub (Jun 28, 2025): > this is not what I want, since it would grant everybody access to the 192.168.1.0/24 network, which is not desired. No. It blocks all ports (`192.168.1.0/24:0` ends with `:0`), so nobody has permission to proactively establish a connection to 192.168.1.0/24. (ICMP ping may be an exception.)
Author
Owner

@hanjo commented on GitHub (Jun 28, 2025):

ah - that's smart. Okay, let me give that a try. Nevertheless, I think it should be fixed by allowing some possibility to control which routes are announced and which are not. I understand the desire to limit the routes for clarity and I also acknowledge that it leaks details that some people probably would like to avoid, but in the case described above, the current implementation breaks functionality.

@hanjo commented on GitHub (Jun 28, 2025): ah - that's smart. Okay, let me give that a try. Nevertheless, I think it should be fixed by allowing some possibility to control which routes are announced and which are not. I understand the desire to limit the routes for clarity and I also acknowledge that it leaks details that some people probably would like to avoid, but in the case described above, the current implementation breaks functionality.
Author
Owner

@S-NoF3aR commented on GitHub (Jul 2, 2025):

This prevents us from updating to 0.26.1.

I looked into the code: the function for the reduced routes only checks whether there is an ACL rule from this node to Prefix in a route. This would have to be extended by a check “From the prefix of a route to this node”, i. in other words the other way around
e73b2a9fb9/hscontrol/types/node.go (L288C1-L302C2)

a quickfix would look like this:

func (node *Node) CanAccessRoute(matchers []matcher.Match, route netip.Prefix) bool {
	src := node.IPs()

	for _, matcher := range matchers {
		if !matcher.SrcsContainsIPs(src...) && !matcher.DestsContainsIP(src...) {
			continue
		}

		if matcher.SrcsOverlapsPrefixes(route) || matcher.DestsOverlapsPrefixes(route) {
			return true
		}
	}

	return false
}
@S-NoF3aR commented on GitHub (Jul 2, 2025): This prevents us from updating to 0.26.1. I looked into the code: the function for the reduced routes only checks whether there is an ACL rule from this node to Prefix in a route. This would have to be extended by a check “From the prefix of a route to this node”, i. in other words the other way around https://github.com/juanfont/headscale/blob/e73b2a9fb9db82ea5dd1a1a5d554a585188b6b21/hscontrol/types/node.go#L288C1-L302C2 a quickfix would look like this: ```go func (node *Node) CanAccessRoute(matchers []matcher.Match, route netip.Prefix) bool { src := node.IPs() for _, matcher := range matchers { if !matcher.SrcsContainsIPs(src...) && !matcher.DestsContainsIP(src...) { continue } if matcher.SrcsOverlapsPrefixes(route) || matcher.DestsOverlapsPrefixes(route) { return true } } return false } ```
Author
Owner

@nblock commented on GitHub (Jul 2, 2025):

No. It blocks all ports (192.168.1.0/24:0 ends with :0), so nobody has permission to proactively establish a connection to 192.168.1.0/24. (ICMP ping may be an exception.)

This will stop working with the next headscale version, see: https://github.com/juanfont/headscale/pull/2606

@nblock commented on GitHub (Jul 2, 2025): > No. It blocks all ports (`192.168.1.0/24:0` ends with `:0`), so nobody has permission to proactively establish a connection to 192.168.1.0/24. (ICMP ping may be an exception.) This will stop working with the next headscale version, see: https://github.com/juanfont/headscale/pull/2606
Author
Owner

@ArcticLampyrid commented on GitHub (Jul 3, 2025):

Then we can change the workaround to

    {
      "action": "accept",
      "proto": "icmp",
      "src": [
        "*"
      ],
      "dst": [
        "192.168.1.0/24:*",
        "group:routers:*"
      ]
    }

or just use a fake port:

    // assume nobody will really use port 11
    {
      "action": "accept",
      "src": [
        "*"
      ],
      "dst": [
        "192.168.1.0/24:11",
        "group:routers:11"
      ]
    }
@ArcticLampyrid commented on GitHub (Jul 3, 2025): Then we can change the workaround to ``` { "action": "accept", "proto": "icmp", "src": [ "*" ], "dst": [ "192.168.1.0/24:*", "group:routers:*" ] } ``` or just use a fake port: ``` // assume nobody will really use port 11 { "action": "accept", "src": [ "*" ], "dst": [ "192.168.1.0/24:11", "group:routers:11" ] } ```
Author
Owner

@kradalby commented on GitHub (Sep 10, 2025):

This should be fixed in https://github.com/juanfont/headscale/pull/2767, give it a go please.

@kradalby commented on GitHub (Sep 10, 2025): This should be fixed in https://github.com/juanfont/headscale/pull/2767, give it a go please.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/headscale#1026