@@ -152,6 +152,9 @@ func run(ctx context.Context, args []string, stdout, stderr io.Writer) error {
152152 return err
153153 }
154154 st .Profiles = stateProfiles (runtimeProfiles )
155+ if err := ensureHostIPs (& st ); err != nil {
156+ return err
157+ }
155158 if err := store .Save (st ); err != nil {
156159 return err
157160 }
@@ -251,6 +254,9 @@ func configureSyntheticPools(st *state.State, ipv4Override, ipv6Override string)
251254 if strings .TrimSpace (family .override ) != "" && oldNormalized != normalized {
252255 st .Leases = discardSyntheticLeases (st .Leases , family .ipv6 )
253256 * family .nat = netip.Addr {}
257+ if ! family .ipv6 {
258+ st .DNSIP = netip.Addr {}
259+ }
254260 }
255261 * family .current = normalized
256262 }
@@ -286,8 +292,8 @@ func discardSyntheticLeases(leases []state.EffectiveLease, ipv6 bool) []state.Ef
286292 return out
287293}
288294
289- func ensureNATIPs (st * state.State ) error {
290- used := make (map [netip.Addr ]bool , len (st .Leases )+ 2 )
295+ func ensureHostIPs (st * state.State ) error {
296+ used := make (map [netip.Addr ]bool , len (st .Leases )+ 3 )
291297 for _ , lease := range st .Leases {
292298 if lease .EffectiveIP .IsValid () {
293299 used [lease .EffectiveIP ] = true
@@ -307,33 +313,68 @@ func ensureNATIPs(st *state.State) error {
307313 return fmt .Errorf ("parse effective %s pool %q: %w" , family .name , family .poolRaw , err )
308314 }
309315 pool = pool .Masked ()
310- // Keep the prefix base unassigned. In particular, using the IPv4
311- // network address as the aggregate TUN's point-to-point identity can
312- // make Darwin route packets DNATed to that address back out the TUN
313- // instead of delivering them locally.
314- if current := * family .current ; current .IsValid () && current != pool .Addr () && current .Is6 () == family .ipv6 && pool .Contains (current ) && ! used [current ] {
315- used [current ] = true
316+ if validReservedIP (* family .current , pool , family .ipv6 , used ) {
317+ used [* family .current ] = true
316318 continue
317319 }
318- * family .current = netip.Addr {}
319- for ip := pool .Addr ().Next (); pool .Contains (ip ); ip = ip .Next () {
320- if ! ip .IsValid () {
321- break
322- }
323- if used [ip ] || ip == tailmixdns .ServiceIP () {
324- continue
325- }
326- * family .current = ip
327- used [ip ] = true
328- break
329- }
320+ * family .current = firstFreePoolIP (pool , used )
330321 if ! family .current .IsValid () {
331322 return fmt .Errorf ("effective %s pool %v has no free address for the host NAT" , family .name , pool )
332323 }
324+ used [* family .current ] = true
325+ }
326+
327+ pool , err := netip .ParsePrefix (st .SyntheticPool )
328+ if err != nil {
329+ return fmt .Errorf ("parse effective IPv4 pool %q: %w" , st .SyntheticPool , err )
330+ }
331+ pool = pool .Masked ()
332+ if validReservedIP (st .DNSIP , pool , false , used ) {
333+ return nil
334+ }
335+ st .DNSIP = netip.Addr {}
336+ preferred := poolIPAtOffset (pool , 53 )
337+ if preferred .IsValid () && ! used [preferred ] {
338+ st .DNSIP = preferred
339+ } else {
340+ st .DNSIP = firstFreePoolIP (pool , used )
341+ }
342+ if ! st .DNSIP .IsValid () {
343+ return fmt .Errorf ("effective IPv4 pool %v has no free address for MagicDNS" , pool )
333344 }
334345 return nil
335346}
336347
348+ func validReservedIP (ip netip.Addr , pool netip.Prefix , ipv6 bool , used map [netip.Addr ]bool ) bool {
349+ return ip .IsValid () && ip != pool .Addr () && ip .Is6 () == ipv6 && pool .Contains (ip ) && ! used [ip ]
350+ }
351+
352+ func firstFreePoolIP (pool netip.Prefix , used map [netip.Addr ]bool ) netip.Addr {
353+ for ip := pool .Addr ().Next (); pool .Contains (ip ); ip = ip .Next () {
354+ if ! ip .IsValid () {
355+ break
356+ }
357+ if ! used [ip ] && ip != tailmixdns .ServiceIP () {
358+ return ip
359+ }
360+ }
361+ return netip.Addr {}
362+ }
363+
364+ func poolIPAtOffset (pool netip.Prefix , offset int ) netip.Addr {
365+ ip := pool .Addr ()
366+ for range offset {
367+ ip = ip .Next ()
368+ if ! ip .IsValid () {
369+ return netip.Addr {}
370+ }
371+ }
372+ if ! pool .Contains (ip ) {
373+ return netip.Addr {}
374+ }
375+ return ip
376+ }
377+
337378func parseProfileSpec (raw string ) (profileSpec , error ) {
338379 var spec profileSpec
339380 if strings .TrimSpace (raw ) == "" {
@@ -605,11 +646,13 @@ func assignEffectiveIPs(st state.State, statuses []tailmixprofile.Status) (activ
605646 familyExisting = append (familyExisting , lease )
606647 }
607648 }
608- reserved := st .NATIP
649+ reserved := []netip. Addr { st .NATIP }
609650 if family .ipv6 {
610- reserved = st .NATIPv6
651+ reserved = []netip.Addr {st .NATIPv6 }
652+ } else {
653+ reserved = append (reserved , st .DNSIP )
611654 }
612- plan , assignErr := effectiveip .NewAllocator (pool , familyExisting , reserved ).Assign (familyNodes )
655+ plan , assignErr := effectiveip .NewAllocator (pool , familyExisting , reserved ... ).Assign (familyNodes )
613656 if assignErr != nil {
614657 return nil , nil , fmt .Errorf ("assign effective %s addresses: %w" , family .name , assignErr )
615658 }
@@ -661,7 +704,7 @@ func buildTUNPlan(st state.State, statuses []tailmixprofile.Status) (tunPlan, er
661704 st .Profiles = slices .Clone (st .Profiles )
662705 st .Leases = slices .Clone (st .Leases )
663706 updateProfileMetadata (& st , statuses )
664- if err := ensureNATIPs (& st ); err != nil {
707+ if err := ensureHostIPs (& st ); err != nil {
665708 return tunPlan {}, err
666709 }
667710 activeLeases , allLeases , err := assignEffectiveIPs (st , statuses )
@@ -984,14 +1027,19 @@ func tunConfigWithPolicy(st state.State, statuses []tailmixprofile.Status, lease
9841027 }
9851028 }
9861029 }
987- serviceIP := tailmixdns .ServiceIP ()
988- for key , source := range table .Sources {
989- if source .HostIP == serviceIP {
990- return packetmap.Table {}, hosttun.Config {}, fmt .Errorf ("profile %q host NAT IP conflicts with MagicDNS service IP %v" , key .ProfileID , serviceIP )
1030+ serviceIPs := []netip.Addr {st .DNSIP , tailmixdns .ServiceIP ()}
1031+ for _ , serviceIP := range serviceIPs {
1032+ if ! serviceIP .IsValid () {
1033+ return packetmap.Table {}, hosttun.Config {}, errors .New ("MagicDNS service address is unavailable" )
1034+ }
1035+ for key , source := range table .Sources {
1036+ if source .HostIP == serviceIP {
1037+ return packetmap.Table {}, hosttun.Config {}, fmt .Errorf ("profile %q host NAT IP conflicts with MagicDNS service IP %v" , key .ProfileID , serviceIP )
1038+ }
1039+ }
1040+ if destination , ok := table .Destinations .Lookup (serviceIP ); ok {
1041+ return packetmap.Table {}, hosttun.Config {}, fmt .Errorf ("profile %q effective target IP conflicts with MagicDNS service IP %v" , destination .ProfileID , serviceIP )
9911042 }
992- }
993- if destination , ok := table .Destinations .Lookup (serviceIP ); ok {
994- return packetmap.Table {}, hosttun.Config {}, fmt .Errorf ("profile %q effective target IP conflicts with MagicDNS service IP %v" , destination .ProfileID , serviceIP )
9951043 }
9961044 if ! st .NATIP .IsValid () {
9971045 return packetmap.Table {}, hosttun.Config {}, errors .New ("host IPv4 NAT address is unavailable" )
@@ -1003,10 +1051,17 @@ func tunConfigWithPolicy(st state.State, statuses []tailmixprofile.Status, lease
10031051 break
10041052 }
10051053 }
1006- hostCfg .Routes = append (hostCfg .Routes , hosttun.Route {
1007- Destination : netip .PrefixFrom (serviceIP , serviceIP .BitLen ()),
1008- Source : st .NATIP ,
1009- })
1054+ hostCfg .Routes = append (hostCfg .Routes ,
1055+ hosttun.Route {
1056+ Destination : netip .PrefixFrom (st .DNSIP , st .DNSIP .BitLen ()),
1057+ Source : st .NATIP ,
1058+ },
1059+ hosttun.Route {
1060+ Destination : netip .PrefixFrom (tailmixdns .ServiceIP (), tailmixdns .ServiceIP ().BitLen ()),
1061+ Source : st .NATIP ,
1062+ Optional : true ,
1063+ },
1064+ )
10101065 return table , hostCfg , nil
10111066}
10121067
@@ -1054,7 +1109,7 @@ func routeOverlapsReserved(st state.State, prefix netip.Prefix) bool {
10541109 return true
10551110 }
10561111 }
1057- for _ , addr := range []netip.Addr {st .NATIP , st .NATIPv6 , tailmixdns .ServiceIP ()} {
1112+ for _ , addr := range []netip.Addr {st .NATIP , st .NATIPv6 , st . DNSIP , tailmixdns .ServiceIP ()} {
10581113 if addr .IsValid () && prefix .Addr ().Is6 () == addr .Is6 () && prefix .Contains (addr ) {
10591114 return true
10601115 }
0 commit comments