development
20 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
de49257c8d |
feat: Separate authorised resolvers from what they may forward towards
Modelled on the operator's production configuration, which was supplied as a sample. Name resolution was one permit and one blanket deny, which said who may be queried but had nothing to say about what a resolver is then allowed to do, and pinned the appliance to whatever name server the machine generating the document happened to be using. Two aliases now carry the policy. The internal alias holds the resolvers a client may query, which is the appliance itself by default, and the upstream alias holds what an authorised resolver may forward towards. Eight rules follow, four per transport, because each transport carries a different set of ports. The deny inverts its destination rather than being a blanket deny placed after the permits. That states the policy as it is actually meant, which is that name resolution towards anything unauthorised is refused, and it stays true as the alias is edited instead of depending upon the deny sitting at a later sequence than every permit. The rule permitting an authorised resolver to forward upstream changes nothing while the internal alias holds only the appliance, because traffic the appliance originates is not evaluated by an inbound rule. It is not redundant for that reason. It is what makes the alias worth having: adding a real resolver to the internal alias gives it both directions at once, reached by clients through one rule and forwarding upstream through the other, with nothing further to write. Nothing about name resolution is read from the machine that generates the document any more. A resolver that machine uses is a fact about that machine rather than about the network the appliance joins, and seeding it wrote a site specific address into every document produced. The upstream alias defaults to public resolvers and is overridable, and the host inspection that remains is only for the networks it is attached to, which the parent network selection excludes. The appliance resolves through its own resolver, which is what allows a local record to be defined and answered authoritatively for the networks behind it, and takes its upstream from the wide area network lease. Three settings carry that: no static name server address is written, the lease is permitted to populate the system list, and the local host is preferred so the appliance asks itself first. The resolver is also switched to forwarding. Left recursing it would query the root servers directly and the upstream the joined network expects would never be consulted, so the appliance would resolve correctly while ignoring its own network's resolver, which is not what taking the upstream from the lease is meant to mean. The local zone stays transparent, so a local record is still answered before anything is forwarded. Verified that the document carries the eight rules in the intended order with the deny inverted, that the aliases hold the appliance address on every planned network and the public upstreams, that an explicit upstream list is honoured, that no address belonging to this host appears anywhere in the output, and that every alias named by any rule still resolves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
9ada3b7079 |
feat: Scope the wide area network rule to a parent network alias
The rule that admits a private source over the wide area network interface named every private range as its destination as well as its source. That is wider than the path it exists to open, which is reaching the networks behind this appliance, and it widens further on its own as routes are added, because it permits whatever the appliance happens to be able to route towards rather than what it is responsible for. The destination is now a PARENT_NETWORK_001 alias holding the parent network that the plan resolved, so the rule is scoped to the addresses this appliance actually owns and stays that way. The source keeps the existing RFC1918 alias, so both ends read as what they mean rather than as literal ranges when the rule set is being reviewed in the interface. The alias is generated from the plan rather than written as a constant, so it follows a parent network that was selected at random just as readily as one that was specified. The rule stays enabled. That was raised again and reaffirmed, and the reasoning from when it was first enabled has not changed: the route only decides where the host sends the packet, so shipping the rule disabled means the documented path does not work until an operator finds and enables it. The readme continues to name it as the first thing to disable where the upstream network is not trusted, and that note now also records that the destination is scoped rather than open. Verified that the alias resolves to the parent network on both a specified and a randomly selected plan, that the rule references it, and that every alias named by any rule in the document exists, which is a check worth having now that a rule depends on an alias built from the plan rather than from a constant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dc79fb52db |
feat: Count the virtual local area network tags instead of deriving them
The tag was the third octet of the network address. That reads well while every network happens to occupy a whole octet, and stops being possible the moment one does not, because two networks that share an octet are assigned the same tag. It had already forced a cap on the subnet mask length one commit earlier, since a /25 produced thirty two networks carrying only sixteen distinct tags. The tag is now counted. StartingVlanId gives the first tagged network its tag and defaults to 2, and VlanIdIncrement advances it for each network after that and defaults to 1, so a sixteen network deployment carries the tags 2 through 16. The default leaves the tag of 1 unused, because a switch commonly treats it as its own default network and colliding with that is awkward to diagnose. Counting removes the constraint at its source rather than fencing it off. The tag no longer has anything to do with the address, so it stays unique however the parent network was divided, and the cap on the subnet mask length is lifted back to a /30. The same /25 and /26 that collided now produce thirty one and sixty three distinct tags. The highest tag is computed before the loop runs, so a range that would pass 4094 throws and names the range it would have produced rather than emitting invalid tags. A run starting at 4080 across fifteen networks is accepted and ends exactly at 4094. The increment also makes tag ranges expressible, which a derived tag could never have offered. An increment of ten gives 2, 12, 22 and so on, which is useful when a tag is meant to carry meaning of its own. What is given up is that a tag no longer tells you the address. That was a real convenience, but it was only ever available while the addressing cooperated, and paying for it with a cap on how the parent network could be divided was the worse side of the trade. The screenshot walkthrough is annotated rather than restaged. Those frames are a real run captured while the tag was still derived, so the caption now says so and gives the names the same deployment would produce today. Verified end to end with the defaults, which produced the tags 2 through 16 with matching interface descriptions and vlan devices, and with a /22 parent divided into /26 networks at an increment of ten, which is a combination the previous scheme could not express at all and which produced fifteen distinct tags from 2 to 142. Both exited 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
af715b67d2 |
feat: Expose the subnet mask length that the parent network is divided by
The size of each individual network was fixed at a /24 by a parameter the plan function held privately and the main script never surfaced, so the only way to change how the parent network was divided was to edit the function. It is now ParentNetworkSubnetMaskLength on both, defaulting to 24, which pairs it with ParentNetworkMaskLength and makes the relationship between the two legible: one says how large the whole range is, the other says how large each network inside it is, and the count is what falls out. It applies on both paths. It describes how the parent network is divided rather than how the parent network is chosen, so the mask length that an explicitly supplied parent network carries has no bearing on it. Only ParentNetworkMaskLength is bypassed when a parent network is named, and the distinction is now stated in the help of both parameters rather than left to be inferred. Testing it across a range of values surfaced a constraint that had not been considered. The virtual local area network tag is the third octet of the network address, so a network has to occupy a whole third octet to own a tag. A network shorter than a /24 spans several and takes the first, which stays unique, but a network longer than a /24 occupies only part of one, so several networks share a third octet and are assigned the same tag. Measured against a /20 parent, a /25 produced thirty two networks carrying only sixteen distinct tags, and a /26 produced sixty four carrying sixteen. Duplicate tags on one parent interface are not a valid virtual local area network configuration, so the range is capped at /24 rather than allowed to emit a document the appliance would refuse. Supporting smaller networks would mean decoupling the tag from the address, which is the property that makes the addressing readable, so the cap is the right side of that trade rather than a limitation to be worked around later. Verified with mask lengths from /22 through /26 against both a specified and a randomly selected parent network, confirming that the tags stay distinct at every accepted value, that both rejected values are refused by the range rather than by producing a broken plan, and that the existing guard against a subnet mask no longer than the parent mask still fires. An end to end run with a /20 parent divided by /22 produced four networks with tags 244, 248 and 252 and exited 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c60da93b58 |
feat: Name the parent network instead of seeding its selection
The seed was repeatable in the strict sense and unusable in the practical one. Reaching a wanted network meant brute forcing seeds until one happened to land on the right index, and the seed did not even mean the same thing twice, because the index was taken against the candidate list that remains after the host's own networks are excluded. On the development host, which holds a network inside 10.0.0.0/8, one candidate is removed and every index above it shifts by one, so the seed that selects 10.130.240.0/20 there selects the adjacent network on a host with no network in that range. The parent network is now named directly. Supplying it takes it verbatim, so it means the same thing on every host and needs no calculation, and leaving it out selects one at random and reports the exact argument that reproduces the selection. Copying one line out of the summary is now the whole of making a build repeatable. The network count follows from the mask lengths rather than standing beside them as a parameter that could disagree, so a /20 divided into /24 networks is sixteen networks and a /21 is eight. NetworkCount is therefore gone, and the count is changed by choosing a different parent mask length. The base network is restricted to the three ranges RFC 1918 reserves. It only bounds a random selection, so a parent network that was named explicitly is not required to sit inside it, nor to avoid the networks this host is attached to. Both are reported and then honoured, because substituting a different network for one that was named deliberately would defeat the only reason to name it. An unaligned value is aligned to its own mask length and the substitution reported. Naming the supernet ParentNetwork collided with the existing use of that term for the first of the divided networks, which is the one bound directly to the adapter rather than to a tag. That one is now the untagged network, which is what actually distinguishes it, and the VLAN sense of parent is left alone since the parent interface of a tagged interface is standard terminology. One defect found while testing rather than reasoned about: the two paths read the subnet mask from different sources, and Get-NetworkSummary spells it Mask where Get-Subnet spells it SubnetMask, so the host route command emitted an empty mask on the explicit path until it was corrected. Verified across both paths, four parent mask lengths, all three base networks, unaligned input, an out of base network, an overlapping network, and the guard against a network mask that is not longer than the parent mask. Two consecutive explicit runs produce identical addressing across all 48 interface, VLAN and scope entries; the documents themselves differ only in the UUIDs, which are regenerated by design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5bbed33cdd |
feat: Enable the wide area network rule and suppress its reply-to
A route on the host only decides where the packet is sent. It says nothing about whether the packet is admitted once it arrives, so shipping the rule at sequence 40 disabled meant the documented path did not work as documented. The operator followed the route instruction, the traffic reached the interface, and the firewall dropped it. The rule is now enabled. Enabling it also widens the appliance beyond the private switch topology it was added for, since a private source anywhere on the upstream network can now reach the networks behind the appliance. For a lab behind an operator's own router that is the intent, and it is what keeps the appliance reachable for management, but it is a real widening and the readme names it as the first thing to disable if the upstream network is not trusted. Looking at whether the rule alone was now sufficient turned up a third mechanism that was not accounted for. OPNsense adds reply-to automatically to any rule that names an interface and a direction other than any, which forces the reply traffic of a matched connection to the gateway of that interface rather than letting it follow the routing table. This rule names the interface by design, and the source it serves is by design a host on the same segment as that interface, so every reply would have been sent out to the upstream router purely for the router to turn it around and deliver it back onto the segment it came from. Some routers will not hairpin that at all. The behaviour is verified against FilterRule::convertReplyTo in opnsense/core rather than assumed, and the guard there is empty() rather than a comparison, which matters twice. Writing a zero into the element does not suppress the behaviour, because a string holding zero satisfies empty() in PHP, so the element has to carry a one. Floating rules are exempt for the same reason, since the empty interface that makes a rule floating fails the interface check. The blast radius was measured rather than reasoned about. The document emits twenty two interface bound rules with a direction other than any, but the automatic behaviour also needs the interface to resolve to a gateway, and no interface in the document defines one. Only the wide area network interface acquires a gateway at all, dynamically, because it is configured for DHCP. That makes this rule the only one affected and the only one that sets the element. The wording around the private network block is reworked throughout to say that it is disabled rather than cleared, and to say what that accomplishes, which is that traffic can ingress from the host. The block discards any packet whose source falls within the private ranges and is applied ahead of the rule set, so with it enabled every packet the host sent would be discarded on arrival and no rule could have permitted it, because the packet would never have reached the rules. The deployment summary drops the step that asked the operator to enable the rule and states that the firewall side is already in place, leaving the route as the only outstanding action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a98967ebc4 |
fix: Decide the host route by the local area network switch
The route into the lab is needed because the host has no adapter behind the appliance, and that is decided by the local area network switch rather than the wide area network one. Gating the offer on the wide area network switch was gating on the wrong thing. That switch is external in almost every deployment, because it is what gives the machines behind the appliance a path to the internet, so the gate passed nearly always and the summary printed a route for the shipped internal topology where the host already has a path and the route is redundant. When it did decline, it declined for a reason about reachability rather than the reason that matters. The two questions are now separated. Whether the route is needed is answered by the local area network switch, through the count of management operating system adapters attached to it, which is zero on a private switch and one on an internal one. Whether the route can work is answered by the wide area network switch as before. That yields three outcomes rather than two, and the third is the one worth reporting: a route that is required and cannot presently be created, along with the reason and the remedy. Reusing an existing local area network switch also rejected a private one. The candidate filter required the type to be internal, so a private switch under the expected name fell through to the name resolver and an internal switch was built beside it under a generated name, quietly producing the opposite of the topology the operator had built by hand. Both types are accepted now, and the requested type no longer filters the candidates at all, since the type is the whole difference between the two topologies. A mismatch is reported as a warning and the existing switch is reused as it is, because changing the type of a switch would detach every virtual machine already connected to it. A LANSwitchType parameter makes the transition a flag rather than a switch built by hand, which also means the deployment marker is applied and the removal path can clean it up afterwards. It defaults to internal, so the shipped behaviour is unchanged. The host adapter rename needed no change, since it already skips a switch for which no virtual adapter is found. Verified by creating a throwaway private switch, which reported its type as private, matched the widened filter, and yielded no management operating system adapter and no virtual adapter before being removed. All three route outcomes were exercised, and the wide area network reachability test still passes for the external switch and declines for both internal ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d8e5bebf57 |
feat: Offer a routed host path into the lab over the wide area network
The default topology attaches the host to an internal local area network switch, which gives it an adapter inside the lab. That adapter then takes a lease from Kea on the parent network, and the lease carries a default gateway, so the host ends up holding two of them. Which one wins becomes a matter of interface metrics rather than intent. The alternative is to drop the host adapter entirely, use a private switch for the local area network, and reach every network behind the appliance through one low priority static route towards its wide area network address. Three pieces make that possible. A rule at sequence 40 permits a private source to reach a private destination over the wide area network interface. It is bound to the interface rather than floating, because a floating rule would apply it everywhere and widen it far past the single path intended, and it is created disabled, because that interface faces the upstream network and an enabled rule of this shape is an unsolicited inbound path into the lab. Enabling it is a decision rather than a default, and it is the only rule in the document emitted disabled. The private network block was already cleared on that interface, which turns out to be a precondition rather than a coincidence. The block is applied ahead of the rule set, so it would discard the traffic before sequence 40 was ever evaluated. The bogon block stays on, since the bogon list excludes the private ranges. The route command is printed in the deployment summary, carrying the supernet and dotted mask of the block that was selected, so one route covers the whole range and no future network needs adding. The address itself cannot be resolved ahead of time because the interface is configured for DHCP and holds no lease until the appliance boots, so the command is printed with a placeholder and the operator reads the address from the console banner. The route is offered only when it would work. A static route towards that address is only reachable when the address is a single hop away, which holds when the switch is external and the management operating system still shares the adapter, since both then lease from the same upstream network. A private switch leaves the host with no interface into the segment, and an internal one carries no address server to lease a reachable address. In those cases the summary reports that the route is not offered rather than printing one that would fail on an unreachable gateway. Verified against the live host, which offered the route for the external switch and declined for both internal ones, and the printed command was executed against a documentation prefix to confirm the syntax and the metric before being removed again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e9a8c59150 |
fix: Share each shaping pipe instead of masking it per address
A dummynet mask does not subdivide a pipe. It instantiates a separate copy of the pipe for every distinct value of the masked field, each with the full configured bandwidth, so masking by address gave every member of a profile its own complete allowance. Five members of a 20 Mbit profile would have carried 100 Mbit between them, and a constrained link would have grown faster as hosts were added to it, which is the opposite of a bottleneck. Every pipe now carries no mask and is a single shared bucket, so the members of a profile contend for the bandwidth as hosts behind a real link do. The contention is the behaviour being reproduced. The corollary, now documented, is that one profile is one link. Two independent constrained paths need two profiles, and ten profiles provide ten independent buckets per direction. The rules stay floating rather than bound to WAN. The intent is to imitate constrained links between the networks behind the router, one router standing in for what would otherwise be several, so that one client reaching one server can be slow while others are fast. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
5d88376499 |
feat: Turn traffic shaping into a set of named scenarios
A single inbound and outbound pair meant deciding the bandwidth up front. Ten scenarios are now emitted instead, spanning a badly congested connection through to gigabit, so imposing one is a matter of choosing which alias to put an address into. 512KBPS 1MBPS 5MBPS 10MBPS 25MBPS 45MBPS 100MBPS 250MBPS 500MBPS 1000MBPS Each scenario produces a matched set that carries its own name throughout, so the intent is legible in an alphabetical alias list rather than requiring the pipe to be opened to discover what it does: - Two aliases, INBOUND_45MBPS and OUTBOUND_45MBPS, both empty - Two pipes carrying the same names, one masked per destination address and one per source address - Two rules binding one to the other An address placed in an inbound and an outbound alias of different speeds gets an asymmetric link, which is what most real connections are. An address should appear in at most one of each, since the rules are quick and the profiles are emitted slowest first, so the slower of the two would win. The bandwidth parameters are removed. The scenarios are fixed, which is the point of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
83c6280568 |
feat: Add traffic shaping that is imposed by editing an alias
Two shaper pipes are created and enabled, and are bound to two firewall aliases that start empty. Shaping is imposed later by adding an address or a network to an alias rather than by building a shaper from scratch. An empty alias produces an empty table, which matches nothing, so both rules are live but inert until then. The shaping is attached to filter rules rather than to shaper rules. A shaper rule declares its source and destination as NetworkField, whose validator only accepts a literal address or the word any, so it cannot reference an alias. A filter rule uses NetworkAliasField for both and additionally carries shaper1 and shaper2, which relate to a shaper pipe or queue. Binding the pipes through filter rules is therefore the only construction that lets an alias drive the shaping. The shaper rule collection is emitted empty on purpose. Two rules rather than one, because a pipe carries traffic in one direction only. Rule 500 matches an address as a source and shapes what it sends, and rule 501 matches it as a destination and shapes what it receives, so an address placed in both aliases is shaped in both directions. Each pipe carries a mask, source address for outbound and destination address for inbound, so the limit applies per address instead of being shared across every entry in the alias. Shaping is applied at layer three, so traffic between two hosts on the same network is switched and is out of scope by construction, while inter network and internet traffic is routed and can be shaped with full layer three context. Bandwidths default to 100 Mbit and are exposed, and SkipTrafficShaping omits the pipes, the aliases and the rules entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a21add5b6e |
fix: Return the Kea socket type to raw
Raw is what the appliance ships with, and it is the only one of the two that reliably answers a client which holds no address yet. That is precisely the preboot execution environment case, so defaulting to udp risked breaking the netboot path this deployment sets up. The udp socket remains available through DHCPSocketType for an environment that requires it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6e86c481a4 |
feat: Reserve a PXE server, float the shared rules, and soften the defaults
Credentials: - The stock appliance password is retained by default. It is typed at a console before being changed, and the appliance prompts for a change on first sign in regardless - GenerateRootPassword opts into a random password, now sixteen characters without symbols rather than twenty with them Preboot execution environment: - Get-HyperVNextAvailableMACAddress reads the next dynamic address the hypervisor will issue, treating the six bytes as one integer so an offset carries correctly across octets - That address is reserved within Kea for a PXE server on the Infrastructure network, which allows the reservation to exist before the virtual machine that will carry the address does - The reserved address populates a PXE_SERVERS_001 alias, becomes next_server and tftp_server_name within every scope, and is reachable through a floating rule from RFC1918 - The reservation is skipped with a warning on a host that does not run the hypervisor, so the document still generates Rules: - The management, jump host, name resolution and PXE rules are now floating. They were identical on every interface, so a sixteen network policy falls from 87 rules to 28 - Descriptions no longer name the network they were generated for, since a floating rule applies everywhere - The RFC1918 alias now has a purpose as the source of the internal permits Kea: - The socket type defaults to udp and is exposed as DHCPSocketType. Raw remains the safer choice for a client that holds no address yet - Option data auto collection is enabled Elsewhere: - Outbound network address translation moves to hybrid, with no manual rules - Zone roles carry three digit indexes, so Servers_Zone_001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
75d3dcb69c |
feat: Size the appliance for the live image and document the operator sequence
Memory: - The virtual machine now defaults to 4 GB rather than 2 GB. The live image runs from memory before it is installed and refuses to start below roughly 3 GB, so the previous default failed during the import phase rather than later. The dynamic memory floor moves to 2 GB to match Configuration disk: - The disk that carries the configuration document now defaults to 4 GB rather than 256 MB. It is dynamically expanding, so nothing more is consumed on the host, but the installer reports the declared size and the operator has to pick this disk out by size at the importer prompt. Four gigabytes against a thirty two gigabyte boot disk is unmistakable - The size is exposed as ConfigurationDiskSizeBytes Boot order: - The disc drive was placed first, which meant the installer booted again on every subsequent start until the media was detached by hand - The boot disk is now first and the disc drive second. The firmware falls through a disk that carries no boot loader, so the installer still boots while the disk is empty, and the installed appliance takes precedence by itself afterwards Documentation: - The readme now carries the full operator sequence as two phases: importing the configuration from da1, then logging in as installer, choosing UFS, installing onto da0, confirming the destroy and rebooting - Both disks are documented with the sizes that identify them Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0c61eda44f |
feat: Name the edge interfaces WAN and LAN, and stop Kea contending for its sockets
Naming: - The wide area network interface is now described as WAN rather than WAN_0000 - The untagged parent network is now named LAN rather than Core - The Storage zone becomes VPN Kea: - A stock installation enables dnsmasq and hands it the address pool. Two services cannot bind the same DHCP socket and the loser is the one that fails to start, so dnsmasq is now left with no DHCP interfaces, its enable and dhcp_ranges nodes are removed, and the legacy dhcpd sections are removed outright - service_sockets_max_retries and service_sockets_retry_wait_time are set, so a VLAN interface that is not ready when the service starts is retried within a bounded window rather than the start appearing to hang - The socket type stays raw on purpose, because udp is a documented cause of clients failing to obtain a lease - Both retry values are exposed as parameters Reporting: - The deployment summary described the planned VLAN list as the trunk contents. The trunk has carried the whole 1-4094 range since it was widened, so the summary now reports what the trunk actually carries and lists the planned VLANs separately Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3914d52123 |
fix: Resolve the release directory index and stop errors being masked
Running the script with no parameters failed at the media acquisition stage with "You cannot call a method on a null-valued expression", which described neither the cause nor the location. Release resolution: - The expression that parses the mirror release directory index required the version to follow href=" immediately, so it matched nothing on a mirror that emits href="./26.1/" rather than href="26.1/", which is what the default mirror does - It now tolerates either quoting style and any leading relative fragment, and resolves 26.1.6 against the live mirror Error reporting: - The main catch captured $Error[0] rather than the error record it was handed, and delegated straight to the toolkit error handler - That handler dereferences InvocationInfo.Line.Trim(), which throws when the property is not populated, and the resulting failure replaced the original diagnostic entirely - The error record is now captured directly, and its message, category, script, line number, code and stack trace are reported before anything else is attempted - The delegation to the toolkit handler is wrapped so that a failure inside it can no longer hide the real cause Name prefix: - The main script still forced a prefix of LAB onto every generated name after the prefix was made optional, so interfaces were named LAB_129_Management rather than 129_Management - The prefix is now only passed through when one has actually been supplied Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
98d5863b8c |
feat: Widen the DHCP pools and split the DNS port aliases per transport
DHCP pools: - Each pool now spans every usable address within its network rather than a hundred address window, so a /24 hands out 253 addresses - Only the network address, the broadcast address, and the address of the firewall itself are excluded - When the firewall does not sit at the first host address the pool is emitted as two ranges either side of it, which Kea accepts as a newline separated pools value - DHCPPoolStartHostOffset and DHCPPoolEndHostOffset are now optional overrides that are clamped to the usable range, and the firewall is carved out of them as well - The plan object gained DHCPPoolRangeList and DHCPPoolAddressCount, and a network too small to yield a usable address warns instead of emitting an invalid pool DNS port aliases: - DNS_PORTS_001 becomes DNS_PORTS_TCP_001 and DNS_PORTS_UDP_001 - TCP carries 53 and 853, UDP carries 53, 853 and 5353, because multicast name resolution is UDP only - The allow and block rules are emitted once per transport rather than once as TCP/UDP, so each references the alias that belongs to it Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c470c51a9a |
feat: Rank the external switch adapter and handle the network interruption
The physical adapter for a new external virtual switch was previously the first operational one by interface index, which could bind a wireless adapter or an adapter already carrying another virtual switch, and the connectivity drop that the bind causes was neither announced nor waited out. Adapter selection: - Exclude adapters already bound to an existing virtual switch - Rank candidates so ethernet always beats wireless, and within each class an adapter carrying a default gateway wins because it is the one providing upstream connectivity today - Break ties on receive link speed, then interface index - Log every candidate with its rank, and the reason the winner was chosen - PreferredNetAdapterName, surfaced as WANNetAdapterName, overrides the ranking and matches on either the adapter name or its interface description - Only enable embedded teaming on a server operating system, across ethernet adapters, and never when the selected adapter is wireless Network interruption: - Warn before binding, naming the adapter, with an extra warning when it is wireless because a bridged attachment interrupts for longer - Pause NetworkInterruptionWarningDelay, default ten seconds, so an operator can interrupt the script before a remote session drops - Wait up to ConnectivitySettleTimeout, default ninety seconds, for the host virtual adapter to come up with a non APIPA address, logging how long it took, and warn rather than fail when it times out Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
bc3dd6c535 |
feat: Add idempotent OPNsense virtual firewall deployment for Hyper-V
Deploys a fully preconfigured OPNsense appliance into a Hyper-V lab in a single execution, re-running safely because every stage detects the current state before it acts. Main script: - Hyper-V platform detection and installation, exiting 3010 only when the hypervisor itself needs a restart - Random /20 block selection out of a private base network, divided into /24 networks whose VLAN tag is the third octet of their own network address - Zone based roles, with five server zones paired by index to five client zones, plus Management, Infrastructure, DMZ, Storage and Guest - Generated OPNsense config.xml delivered on a FAT32 VHDX at conf/config.xml - Generation 2 virtual machine with secure boot disabled and a LAN trunk carrying VLANs 1-4094 - Marker scoped teardown via RemoveExistingDeployment Toolkit functions: - Save-ToolkitModule, Install-HyperVPlatform, Test-PendingReboot - Expand-CompressedFile, Get-OPNSenseInstallationMedia - Get-HyperVStorageLocation, Get-HostUpstreamDNSConfiguration - New-RandomPassword, New-OPNSensePasswordHash - New-OPNSenseNetworkPlan, New-OPNSenseConfigurationDocument, Save-OPNSenseConfigurationDocument, New-OPNSenseConfigurationDisk - Initialize-OPNSenseVirtualSwitch, New-OPNSenseVirtualMachine, Remove-OPNSenseDeployment Configuration document covers interfaces, VLANs, Kea DHCPv4 scopes with PXE options, Unbound, outbound NAT, six firewall aliases and an ordered rule set that grants management full reach, allows the jump hosts over well known management ports, forces name resolution to approved resolvers, and pairs the client and server zones. Bundles 7-Zip, because the tar.exe included with Windows cannot read a raw bzip2 stream, and BCrypt.Net-Next for the appliance password hash. docs: Add readme with execution flow and generated per function reference docs: Add design specification under .ai/specification Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
de577f1058 | Initial Commit |