P01-T07 - Windows Service Flows
Objective
Keep the base firewall restrictive while allowing the minimum Windows traffic required for the lab: domain controller access, file server access, and functional DHCP relay from workstation VLANs.
This runbook is intentionally a lab bring-up profile. It is designed to get the first domain controller and first workstations functional quickly. It is not the final least-privilege enterprise firewall posture for Active Directory.
Validated lab status
Validated against the live lab on Saturday, July 18, 2026, and re-audited on Sunday, July 19, 2026:
relay-vlan30-workstationswas required forHQ-DC01to serve172.20.30.0/24- DHCP relay failed until explicit
inputexceptions were inserted beforeINPUT - Default Drop - a Linux DHCP test client on
VLAN30was used to reproduce and validate the relay path - the failure mode was visible as repeated
DHCPDISCOVERtraffic with noDHCPOFFERuntil the firewall ordering was corrected - workstation onboarding later exposed a second gap:
DHCPworked butVLAN30 -> HQ-DC01DNS and AD flows were still blocked byFWD - Default Drop - the runbook was corrected to use explicit workstation-to-DC DNS/AD rules before
FWD - Default Drop - a real Windows client on
VLAN30later confirmed: Resolve-DnsName hq-dc01.corp.gntech.mesucceeded- TCP
53,389, and445to172.20.20.11succeeded - workstation domain join and
GPO-Workstations-Hardeningapplication succeeded after those rules were added - the July 19 CHR audit confirmed:
vlan30-workstationsrelay remains enabled toward172.20.20.11- all VLAN gateway interfaces are running on
ether2 - Cloudflare container support is enabled with
/system device-mode print cloudflared:latestis running- CHR can ping
172.20.20.11,1.1.1.1, andcloudflare.com - duplicate workstation-to-
HQ-FS01SMB rules can appear if direct recovery blocks are pasted more than once; clean them before final validation
Inputs
| Key | Value |
|---|---|
| Domain controller | HQ-DC01 = 172.20.20.11 |
| File server | HQ-FS01 = 172.20.20.21 |
| Server VLAN | 172.20.20.0/24 |
| Workstation VLAN | 172.20.30.0/24 |
| Hypervisor VLAN | 172.20.100.0/24 |
Target State
| Property | Value |
|---|---|
| DHCP relay | vlan30-workstations -> HQ-DC01 |
| DHCP relay firewall posture | Explicit input allows before INPUT - Default Drop |
| Workstation to DC | Minimum required AD/DNS/GPO flow allowed explicitly |
| Workstation to file server | SMB-only |
| Default posture | Still deny by default |
Prechecks
- Complete
P01-T06. HQ-DC01exists on172.20.20.11.HQ-FS01exists on172.20.20.21or reserve the address for it now.
Remote DHCP Planning Matrix
Use this matrix before configuring relay on any new deployment. Every VLAN that uses a DHCP server outside its own broadcast domain must be represented here.
| VLAN role | VLAN interface | Gateway IP on CHR | DHCP server IP | Needs relay | Needs input exceptions |
|---|---|---|---|---|---|
| Workstations | vlan30-workstations |
172.20.30.1 |
172.20.20.11 |
Yes | Yes |
| Printers | vlan40-printers |
172.20.40.1 |
Client-specific | If DHCP is remote | If DHCP is remote |
| Corp Wi-Fi | vlan60-corpwifi |
172.20.60.1 |
Client-specific | If DHCP is remote | If DHCP is remote |
| Guest Wi-Fi | vlan70-guestwifi |
172.20.70.1 |
Client-specific | If DHCP is remote | If DHCP is remote |
| Future branch/user VLANs | <vlan-interface> |
<gateway-ip> |
<dhcp-server-ip> |
Decide explicitly | Decide explicitly |
Decision rule:
Needs relay = Yeswhenever the VLAN gateway is onCHRand the DHCP server is not in that same VLAN.Needs input exceptions = Yeswhenever relay is enabled on that VLAN.
Execution
- Create address lists for lab server roles:
/ip firewall address-list
add list=HQ-DOMAIN-CONTROLLERS address=172.20.20.11 comment="HQ-DC01"
add list=HQ-FILE-SERVERS address=172.20.20.21 comment="HQ-FS01"
- Add DHCP relay for workstation clients so the Windows DHCP service on
HQ-DC01can serveVLAN 30.
Do not assume inter-VLAN DHCP will work automatically. In this lab, HQ-DC01 lives in VLAN 20 while clients live in VLAN 30, so CHR must relay the client broadcast to 172.20.20.11.
In current RouterOS, comment= is not reliably accepted in /ip dhcp-relay add. Use a stable name= and idempotent creation logic instead:
:if ([:len [/ip dhcp-relay find where name="relay-vlan30-workstations"]] = 0) do={
/ip dhcp-relay add name=relay-vlan30-workstations \
interface=vlan30-workstations \
dhcp-server=172.20.20.11 \
local-address=172.20.30.1 \
disabled=no
}
- Allow the relay traffic in the
inputchain beforeINPUT - Default Drop.
Without these rules, the relay can exist and still fail because the router drops the client request or the server reply before the relay process can use it.
Use direct place-before=[find ...] targeting. Do not store INPUT - Default Drop in a local variable for this step; in practice that pattern can fail with no such item depending on how the shell resolves the dynamic rule reference.
:if ([:len [/ip firewall filter find where comment="INPUT - DHCP Relay Client Requests"]] = 0) do={
/ip firewall filter add chain=input action=accept protocol=udp \
in-interface=vlan30-workstations src-port=68 dst-port=67 \
comment="INPUT - DHCP Relay Client Requests" \
place-before=[find where chain=input comment="INPUT - Default Drop"]
}
:if ([:len [/ip firewall filter find where comment="INPUT - DHCP Relay Server Replies"]] = 0) do={
/ip firewall filter add chain=input action=accept protocol=udp \
src-address=172.20.20.11 dst-port=67-68 \
comment="INPUT - DHCP Relay Server Replies" \
place-before=[find where chain=input comment="INPUT - Default Drop"]
}
Do not append these rules after the final drop and assume the relay is covered. The validated failure mode in this lab was exactly that: relay present, client broadcasts visible on vlan30-workstations, but replies were still dropped by the input chain.
- If you need the exact direct recovery path that was used in the live lab, insert the forward rules directly before
FWD - Default Drop.
This is the fastest known-good recovery method when workstation onboarding is already blocked and you need to restore a real deployment path immediately.
/ip firewall filter
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=udp dst-port=53 comment="FWD - Workstations to DC DNS UDP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=tcp dst-port=53 comment="FWD - Workstations to DC DNS TCP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=tcp dst-port=88,464 comment="FWD - Workstations to DC Kerberos TCP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=udp dst-port=88,123,137,138,389,464 comment="FWD - Workstations to DC Core UDP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=tcp dst-port=135,139,389,445,636,3268,3269,5722 comment="FWD - Workstations to DC Core TCP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.11 protocol=tcp dst-port=49152-65535 comment="FWD - Workstations to DC RPC Dynamic" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.21 protocol=tcp dst-port=445,139 comment="FWD - Workstations to HQ-FS01 SMB TCP" place-before=[find where chain=forward comment="FWD - Default Drop"]
add chain=forward action=accept src-address=172.20.30.0/24 dst-address=172.20.20.21 protocol=udp dst-port=137,138 comment="FWD - Workstations to HQ-FS01 SMB UDP" place-before=[find where chain=forward comment="FWD - Default Drop"]
Use this recovery block only once on a clean chain. If the comments already exist, do not re-add duplicates. Move to the idempotent block below for maintained state.
If this block was pasted more than once during troubleshooting, remove duplicate comments before final validation. Keep the first rule for each comment and remove the extra copies:
{
:local RuleComments {
"FWD - Workstations to DC DNS UDP";
"FWD - Workstations to DC DNS TCP";
"FWD - Workstations to DC Kerberos TCP";
"FWD - Workstations to DC Core UDP";
"FWD - Workstations to DC Core TCP";
"FWD - Workstations to DC RPC Dynamic";
"FWD - Workstations to HQ-FS01 SMB TCP";
"FWD - Workstations to HQ-FS01 SMB UDP"
}
:foreach RuleComment in=$RuleComments do={
:local Seen false
:foreach RuleId in=[/ip firewall filter find where chain=forward comment=$RuleComment] do={
:if ($Seen = false) do={
:set Seen true
} else={
/ip firewall filter remove $RuleId
}
}
}
}
- Create the transitional service-specific forward rules with stable comments.
Do not use a single broad Workstations -> DC allow rule here. The lab now requires the runbook to state the minimum expected AD/DNS/GPO flow explicitly so the sysadmin can validate what is being opened.
/ip firewall filter
:foreach RuleComment in={
"FWD - Workstations to DC DNS UDP";
"FWD - Workstations to DC DNS TCP";
"FWD - Workstations to DC Kerberos TCP";
"FWD - Workstations to DC Core UDP";
"FWD - Workstations to DC Core TCP";
"FWD - Workstations to DC RPC Dynamic";
"FWD - Servers to DC";
"FWD - Hypervisors to DC";
"FWD - Workstations to HQ-FS01 SMB TCP";
"FWD - Workstations to HQ-FS01 SMB UDP"
} do={
:local Seen false
:foreach RuleId in=[find where chain=forward comment=$RuleComment] do={
:if ($Seen = false) do={
:set Seen true
} else={
remove $RuleId
}
}
}
:if ([:len [find where comment="FWD - Workstations to DC DNS UDP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=udp dst-port=53 comment="FWD - Workstations to DC DNS UDP"
}
:if ([:len [find where comment="FWD - Workstations to DC DNS TCP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=tcp dst-port=53 comment="FWD - Workstations to DC DNS TCP"
}
:if ([:len [find where comment="FWD - Workstations to DC Kerberos TCP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=tcp dst-port=88,464 comment="FWD - Workstations to DC Kerberos TCP"
}
:if ([:len [find where comment="FWD - Workstations to DC Core UDP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=udp dst-port=88,123,137,138,389,464 comment="FWD - Workstations to DC Core UDP"
}
:if ([:len [find where comment="FWD - Workstations to DC Core TCP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=tcp dst-port=135,139,389,445,636,3268,3269,5722 comment="FWD - Workstations to DC Core TCP"
}
:if ([:len [find where comment="FWD - Workstations to DC RPC Dynamic"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS protocol=tcp dst-port=49152-65535 comment="FWD - Workstations to DC RPC Dynamic"
}
:if ([:len [find where comment="FWD - Servers to DC"]] = 0) do={
add chain=forward action=accept src-address=172.20.20.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS comment="FWD - Servers to DC"
}
:if ([:len [find where comment="FWD - Hypervisors to DC"]] = 0) do={
add chain=forward action=accept src-address=172.20.100.0/24 dst-address-list=HQ-DOMAIN-CONTROLLERS comment="FWD - Hypervisors to DC"
}
:if ([:len [find where comment="FWD - Workstations to HQ-FS01 SMB TCP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-FILE-SERVERS protocol=tcp dst-port=445,139 comment="FWD - Workstations to HQ-FS01 SMB TCP"
}
:if ([:len [find where comment="FWD - Workstations to HQ-FS01 SMB UDP"]] = 0) do={
add chain=forward action=accept src-address=172.20.30.0/24 dst-address-list=HQ-FILE-SERVERS protocol=udp dst-port=137,138 comment="FWD - Workstations to HQ-FS01 SMB UDP"
}
- Move the transitional forward rules above
FWD - Default Dropin the intended order. Do not use absolute rule numbers for this task. In RouterOS, absolute moves are brittle and can fail withinvalid value for argument numbersorcan not move object before itselfwhen comments are duplicated or prior ordering differs.
{
:local DesiredOrder {
"FWD - Workstations to DC DNS UDP";
"FWD - Workstations to DC DNS TCP";
"FWD - Workstations to DC Kerberos TCP";
"FWD - Workstations to DC Core UDP";
"FWD - Workstations to DC Core TCP";
"FWD - Workstations to DC RPC Dynamic";
"FWD - Servers to DC";
"FWD - Hypervisors to DC";
"FWD - Workstations to HQ-FS01 SMB TCP";
"FWD - Workstations to HQ-FS01 SMB UDP";
"FWD - Guest Isolation";
"FWD - DMZ Isolation"
}
:foreach RuleComment in=$DesiredOrder do={
:foreach RuleId in=[/ip firewall filter find where comment=$RuleComment] do={
:local DefaultDrop [/ip firewall filter find where comment="FWD - Default Drop"]
/ip firewall filter move $RuleId $DefaultDrop
}
}
}
This pattern is safe because every matching rule is moved immediately before the current FWD - Default Drop, preserving the base forward rules above it. If you previously re-ran the add commands and created duplicate comments, the nested :foreach still works.
- Generalize this pattern for future VLANs that must use a DHCP server in another VLAN.
For every additional VLAN with remote DHCP:
- create one relay bound to that VLAN interface
- allow
udp 68 -> 67from that VLAN intoCHR - allow the DHCP server reply back into
CHR - validate with live client renewal, not just config inspection
Example placeholders for future VLANs only. Do not paste this block unchanged into production. Replace every <...> token with the real interface, gateway, and DHCP server values for that VLAN before execution:
:if ([:len [/ip dhcp-relay find where name="relay-<vlan-name>"]] = 0) do={
/ip dhcp-relay add name="relay-<vlan-name>" \
interface=<vlan-interface> \
dhcp-server=<dhcp-server-ip> \
local-address=<vlan-gateway-ip> \
disabled=no
}
Matching input pattern:
:if ([:len [/ip firewall filter find where comment="INPUT - DHCP Relay Client Requests - <vlan-name>"]] = 0) do={
/ip firewall filter add chain=input action=accept protocol=udp \
in-interface=<vlan-interface> src-port=68 dst-port=67 \
comment="INPUT - DHCP Relay Client Requests - <vlan-name>" \
place-before=[find where chain=input comment="INPUT - Default Drop"]
}
:if ([:len [/ip firewall filter find where comment="INPUT - DHCP Relay Server Replies - <vlan-name>"]] = 0) do={
/ip firewall filter add chain=input action=accept protocol=udp \
src-address=<dhcp-server-ip> dst-port=67-68 \
comment="INPUT - DHCP Relay Server Replies - <vlan-name>" \
place-before=[find where chain=input comment="INPUT - Default Drop"]
}
- After the domain controller is operational, continue with
P01-T09to replace these transitional port-specific workstation rules with managed-subnet policy while preserving the DHCP relay path.
Validation
/ip dhcp-relay print detailshows the relay bound tovlan30-workstations./ip firewall filter print detail where chain=inputshows both DHCP relay rules beforeINPUT - Default Drop./ip firewall filter print stats where comment~"DHCP Relay"increments when a client renews.- a live client on
VLAN 30receives a DHCP lease with gateway172.20.30.1and DNS172.20.20.11. - Workstations can join the domain through
HQ-DC01. - Workstations can resolve
hq-dc01.corp.gntech.methrough172.20.20.11. - If
HQ-FS01is in scope for the current deployment wave, workstations can access approved SMB shares onHQ-FS01. - Guest and DMZ isolation still hold.
- The team understands these DC access rules are transitional and should be tightened in
P01-T09. - The transitional workstation-to-DC port-specific rules and SMB rules are positioned above
FWD - Default Drop. - No duplicate forward rules remain for workstation-to-DC or workstation-to-
HQ-FS01SMB comments.
Evidence
- Output of
/ip dhcp-relay print detail - Output of
/ip firewall filter print detail where chain=input - Output of
/ip firewall filter print stats where comment~"DHCP Relay" - Output of
/ip firewall address-list print - Output of
/ip firewall filter print - Output of
/ip firewall filter print stats - Successful DHCP lease evidence from a live
VLAN30client
Rollback
- Remove only the relay and the DHCP relay-specific
inputrules if they are incorrect. - Keep the
INPUT - Default Dropanchor rule intact. - Keep the base NAT and firewall policy intact.