Skip to content

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-workstations was required for HQ-DC01 to serve 172.20.30.0/24
  • DHCP relay failed until explicit input exceptions were inserted before INPUT - Default Drop
  • a Linux DHCP test client on VLAN30 was used to reproduce and validate the relay path
  • the failure mode was visible as repeated DHCPDISCOVER traffic with no DHCPOFFER until the firewall ordering was corrected
  • workstation onboarding later exposed a second gap: DHCP worked but VLAN30 -> HQ-DC01 DNS and AD flows were still blocked by FWD - Default Drop
  • the runbook was corrected to use explicit workstation-to-DC DNS/AD rules before FWD - Default Drop
  • a real Windows client on VLAN30 later confirmed:
  • Resolve-DnsName hq-dc01.corp.gntech.me succeeded
  • TCP 53, 389, and 445 to 172.20.20.11 succeeded
  • workstation domain join and GPO-Workstations-Hardening application succeeded after those rules were added
  • the July 19 CHR audit confirmed:
  • vlan30-workstations relay remains enabled toward 172.20.20.11
  • all VLAN gateway interfaces are running on ether2
  • Cloudflare container support is enabled with /system device-mode print
  • cloudflared:latest is running
  • CHR can ping 172.20.20.11, 1.1.1.1, and cloudflare.com
  • duplicate workstation-to-HQ-FS01 SMB 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-DC01 exists on 172.20.20.11.
  • HQ-FS01 exists on 172.20.20.21 or 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 = Yes whenever the VLAN gateway is on CHR and the DHCP server is not in that same VLAN.
  • Needs input exceptions = Yes whenever relay is enabled on that VLAN.

Execution

  1. 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"
  1. Add DHCP relay for workstation clients so the Windows DHCP service on HQ-DC01 can serve VLAN 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
}
  1. Allow the relay traffic in the input chain before INPUT - 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.

  1. 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
            }
        }
    }
}
  1. 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"
}
  1. Move the transitional forward rules above FWD - Default Drop in the intended order. Do not use absolute rule numbers for this task. In RouterOS, absolute moves are brittle and can fail with invalid value for argument numbers or can not move object before itself when 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.

  1. 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 -> 67 from that VLAN into CHR
  • 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"]
}
  1. After the domain controller is operational, continue with P01-T09 to replace these transitional port-specific workstation rules with managed-subnet policy while preserving the DHCP relay path.

Validation

  • /ip dhcp-relay print detail shows the relay bound to vlan30-workstations.
  • /ip firewall filter print detail where chain=input shows both DHCP relay rules before INPUT - Default Drop.
  • /ip firewall filter print stats where comment~"DHCP Relay" increments when a client renews.
  • a live client on VLAN 30 receives a DHCP lease with gateway 172.20.30.1 and DNS 172.20.20.11.
  • Workstations can join the domain through HQ-DC01.
  • Workstations can resolve hq-dc01.corp.gntech.me through 172.20.20.11.
  • If HQ-FS01 is in scope for the current deployment wave, workstations can access approved SMB shares on HQ-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-FS01 SMB 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 VLAN30 client

Rollback

  • Remove only the relay and the DHCP relay-specific input rules if they are incorrect.
  • Keep the INPUT - Default Drop anchor rule intact.
  • Keep the base NAT and firewall policy intact.