Repository: juanfont/headscale

Issue:
[Bug] DNS Resolve Error for the base_domain(server_rul) at v0.24.0
### 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

1. `config.yaml`
```yaml
server_url: example.net
dns:
  magic_dns: true
  base_domain: example.net
``` 

2. try to `ping example.net`
```bash
➜  ~ ping example.net
ping: cannot resolve example.net: Unknown host
```

3. or try to open `https://example.net/apple`

<img width="860" alt="Image" src="https://github.com/user-attachments/assets/e9af8cf6-6907-4bcd-b0e6-acf2ddcf0aca" />

4. magic_dns works
```bash
➜  ~ ping test              
PING test.example.net (100.64.0.7): 56 data bytes
64 bytes from 100.64.0.7: icmp_seq=0 ttl=64 time=253.130 ms
64 bytes from 100.64.0.7: icmp_seq=1 ttl=64 time=86.409 ms
^C
--- test.example.net ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 86.409/169.769/253.130/83.361 ms
➜  ~ ping test.example.net    
PING test.example.net (100.64.0.7): 56 data bytes
64 bytes from 100.64.0.7: icmp_seq=0 ttl=64 time=94.611 ms
64 bytes from 100.64.0.7: icmp_seq=1 ttl=64 time=103.333 ms
^C
--- test.example.net ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 94.611/98.972/103.333/4.361 ms
```

### Expected Behavior

it should resolve `base_domain`?, i mean it should bypass the DNS querying

### Steps To Reproduce

see up

### Environment

```markdown
- OS: MacOS
- Headscale version: 0.24.0
- Tailscale version: 1.78.1
```

### Runtime environment

- [x] Headscale is behind a (reverse) proxy
- [x] Headscale runs in a container

### Anything else?

_No response_

Hints from issue comments:
@kradalby: @motiejus I wonder if this is related to https://github.com/juanfont/headscale/pull/2248/files#diff-f4787c8465217f16007cdbaf693a77747964495a8e1e8e50acb411d89c38e68bR354-R355

Can you have a look?@motiejus: When I made the PR, I assumed `base_domain` can be the same as `serverURL` (as you can see from the test, it's explicitly handled). Why wouldn't it work on the network level?

I can adjust the original PR to explicitly not allow the same base_domain and serverURL, but it would be great to know why.

In my naive understanding, "magic DNS" is only supposed to return addresses for first-level sub-domains of `base_domain`, but not the `base_domain` itself. Is the assumption incorrect?@nsauk: I found a dirty hack to overcome this behaviour:
```
server_url: example.net
dns:
  magic_dns: true
  base_domain: example.net
  nameservers:
    global:
      - 1.1.1.1
      - 1.0.0.1
    split:
      example.net:
        - 1.1.1.1
        - 1.0.0.1
```
This way you can have your public subdomains and headscale nodes mixed.@kradalby: I am adding back a rule to prevent this configuration. It does not work and should not be used, fixed in https://github.com/juanfont/headscale/pull/2544.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f39c3a2b..0eff4ad7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -73,6 +73,11 @@ towards this code.
 The new policy can be used by setting the environment variable
 `HEADSCALE_EXPERIMENTAL_POLICY_V2` to `1`.
 
+#### Other breaking
+
+- Disallow `server_url` and `base_domain` to be equal
+  [#2544](https://github.com/juanfont/headscale/pull/2544)
+
 ### Changes
 
 - Use Go 1.24 [#2427](https://github.com/juanfont/headscale/pull/2427)
diff --git a/hscontrol/types/config.go b/hscontrol/types/config.go
index 588d6a71..a0fcfd45 100644
--- a/hscontrol/types/config.go
+++ b/hscontrol/types/config.go
@@ -33,6 +33,7 @@ const (
 var (
 	errOidcMutuallyExclusive = errors.New("oidc_client_secret and oidc_client_secret_path are mutually exclusive")
 	errServerURLSuffix       = errors.New("server_url cannot be part of base_domain in a way that could make the DERP and headscale server unreachable")
+	errServerURLSame         = errors.New("server_url cannot use the same domain as base_domain in a way that could make the DERP and headscale server unreachable")
 	errInvalidPKCEMethod     = errors.New("pkce.method must be either 'plain' or 'S256'")
 )
 
@@ -999,6 +1000,10 @@ func isSafeServerURL(serverURL, baseDomain string) error {
 		return err
 	}
 
+	if server.Hostname() == baseDomain {
+		return errServerURLSame
+	}
+
 	serverDomainParts := strings.Split(server.Host, ".")
 	baseDomainParts := strings.Split(baseDomain, ".")
 



Test patch:
diff --git a/hscontrol/types/config_test.go b/hscontrol/types/config_test.go
index e7afee69..7ae3db59 100644
--- a/hscontrol/types/config_test.go
+++ b/hscontrol/types/config_test.go
@@ -423,6 +423,7 @@ func TestSafeServerURL(t *testing.T) {
 		{
 			serverURL:  "https://headscale.com",
 			baseDomain: "headscale.com",
+			wantErr:    errServerURLSame.Error(),
 		},
 		{
 			serverURL:  "https://headscale.com",