Why This Post Exists
Before deploying Zeek or Suricata, the sensor needs to see all the traffic worth monitoring. That requires proper segmentation — not just creating VLANs, but making sure devices end up in the right VLAN, that inter-VLAN traffic is routed through the MikroTik (so it can be mirrored), and that the mirror port actually delivers frames to the sensor interface.
This post documents the full configuration from router to switch, the problems I ran into, and the evidence I used to verify each stage actually worked.
Design
Three VLANs on a flat 10.0.x.0/24 scheme:
| VLAN | Name | Subnet | Purpose |
|---|---|---|---|
| 20 | User | 10.0.20.0/24 | Trusted wired + WiFi Home |
| 30 | IoT | 10.0.30.0/24 | Smart home devices |
| 40 | Guest | 10.0.40.0/24 | Visitor WiFi |
Management is on ether5 at 192.168.88.1/24, isolated from the bridge entirely — this
turned out to be a critical design decision, explained below.
Internet
│
[ISP Router] 192.168.1.x
│
[ether1] MikroTik E50UG [ether5]─── Management PC (192.168.88.10)
│
[ether2] trunk: VLANs 20,30,40 tagged
│
[Port 1] TP-Link TL-SG108E
├─[Port 2]───── Wired devices (VLAN 20 access, PVID 20)
├─[Port 3]───── Wired IoT (VLAN 30 access, PVID 30)
├─[Port 4]───── Wired devices (VLAN 20 access, PVID 20)
├─[Port 5]───── Wired devices (VLAN 20 access, PVID 20)
├─[Port 6]───── EAP225 AP #1 (trunk: VLANs 20,30,40 tagged)
├─[Port 7]───── EAP225 AP #2 (trunk: VLANs 20,30,40 tagged)
└─[Port 8]───── Shuttle eth1 (mirror destination, VLAN 1 untagged)
Port mirror source: Ports 1, 6, 7 → Port 8 (both directions)
Success criteria (defined before starting):
- Wired device on Port 2, 4, 5 receives
10.0.20.xvia DHCP - Wired device on Port 3 receives
10.0.30.xvia DHCP - WiFi “Home” SSID receives
10.0.20.x, “IoT” receives10.0.30.x, “Guest” receives10.0.40.x - Inter-VLAN pings to gateway addresses succeed
- Internet access works from all VLANs
- Management at
192.168.88.1always reachable
Phase 1 — MikroTik Router
The Management Lockout Problem
Enabling bridge VLAN filtering on creates an immediate conflict: the bridge needs to be tagged in the VLAN table for inter-VLAN routing, but you cannot simultaneously manage the router through an interface that only accepts tagged frames. Attempting to do this locks you out entirely.
The fix is to separate management before touching VLAN filtering. ether5 was given
a static IP and removed from the bridge first:
/ip address add address=192.168.88.1/24 interface=ether5
/interface bridge port remove [find interface=ether5]
/ip address remove [find interface=bridge]
Evidence of success: Connection by Winbox to 192.168.88.1 remained live throughout all subsequent
configuration phases, including when VLAN filtering was enabled. No safe-mode revert
was triggered.
Building the VLANs
After cleaning up a previous misconfiguration, the new VLAN interfaces were created on the bridge:
# VLAN interfaces
/interface vlan add interface=bridge name=vlan20-user vlan-id=20
/interface vlan add interface=bridge name=vlan30-iot vlan-id=30
/interface vlan add interface=bridge name=vlan40-guest vlan-id=40
# bridge VLAN table (bridge and ether2 carry all three tagged)
/interface bridge vlan add bridge=bridge tagged=bridge,ether2 vlan-ids=20
/interface bridge vlan add bridge=bridge tagged=bridge,ether2 vlan-ids=30
/interface bridge vlan add bridge=bridge tagged=bridge,ether2 vlan-ids=40
# gateway IPs
/ip address add address=10.0.20.1/24 interface=vlan20-user network=10.0.20.0
/ip address add address=10.0.30.1/24 interface=vlan30-iot network=10.0.30.0
/ip address add address=10.0.40.1/24 interface=vlan40-guest network=10.0.40.0
DHCP servers were created for each VLAN with pools in the .100–.254 range. VLAN
filtering was enabled in safe mode last:
/interface bridge set bridge vlan-filtering=yes
/system backup save name=vlan-config-working
MikroTik Evidence Checklist
Interface state:
/interface print
Expected: vlan20-user, vlan30-iot, vlan40-guest all show as running on bridge.
[admin@MikroTik] > /interface print
Flags: R - RUNNING; S - SLAVE
Columns: NAME, TYPE, ACTUAL-MTU, L2MTU, MAX-L2MTU, MAC-ADDRESS
# NAME TYPE ACTUAL-MTU L2MTU MAX-L2MTU MAC-ADDRESS
0 R ether1 ether 1500 1600 2048 F4:1E:57:xx:xx:xx
1 RS ether2 ether 1500 1596 2026 F4:1E:57:xx:xx:xx
2 ether3 ether 1500 1596 2026 F4:1E:57:xx:xx:xx
3 ether4 ether 1500 1596 2026 F4:1E:57:xx:xx:xx
4 ether5 ether 1500 1596 2026 F4:1E:57:xx:xx:xx
;;; defconf
5 R bridge bridge 1500 1596 F4:1E:57:xx:xx:xx
6 R lo loopback 65536 00:00:00:00:00:00
7 R vlan20-user vlan 1500 1592 F4:1E:57:xx:xx:xx
8 R vlan30-iot vlan 1500 1592 F4:1E:57:xx:xx:xx
9 R vlan40-guest vlan 1500 1592 F4:1E:57:xx:xx:xx
Bridge VLAN table:
/interface bridge vlan print
Expected: VLANs 20, 30, 40 each list bridge and ether2 as tagged members.
[admin@MikroTik] > /interface bridge vlan print
Flags: D - DYNAMIC
Columns: BRIDGE, VLAN-IDS, CURRENT-TAGGED, CURRENT-UNTAGGED
# BRIDGE VLAN-IDS CURRENT-TAGGED CURRENT-UNTAGGED
0 bridge 20 bridge
ether2
1 bridge 30 bridge
ether2
2 bridge 40 bridge
ether2
;;; added by pvid
3 D bridge 1 bridge
DHCP leases (after connecting test devices):
/ip dhcp-server lease print
Expected: Leases from dhcp20-user, dhcp30-iot, dhcp40-guest pools visible.
A device on VLAN 20 shows address 10.0.20.x; VLAN 30 shows 10.0.30.x.
[admin@MikroTik] > /ip dhcp-server lease print
Flags: D - DYNAMIC
Columns: ADDRESS, MAC-ADDRESS, HOST-NAME, SERVER, STATUS, LAST-SEEN
# ADDRESS MAC-ADDRESS HOST-NAME SERVER STATUS LAST-SEEN
0 D 10.0.20.250 F4:34:F0:xx:xx:xx dhcp20-user bound 5m38s
1 D 10.0.20.246 CC:5E:F8:xx:xx:xx dhcp20-user bound 5m34s
2 D 10.0.20.245 8C:86:DD:xx:xx:xx dhcp20-user bound 11m15s
3 D 10.0.20.244 70:F1:1C:xx:xx:xx dhcp20-user bound 6m33s
4 D 10.0.20.243 58:D3:49:xx:xx:xx dhcp20-user bound 38s
5 D 10.0.20.241 60:DC:81:xx:xx:xx dhcp20-user bound 10m
6 D 10.0.20.248 4C:60:BA:xx:xx:xx dhcp20-user bound 4m42s
7 D 10.0.20.233 48:E1:5C:xx:xx:xx dhcp20-user bound 3m17s
8 D 10.0.30.253 F8:81:1A:xx:xx:xx dhcp30-iot bound 1m47s
9 D 10.0.20.229 90:9A:4A:xx:xx:xx dhcp20-user bound 2m29s
10 D 10.0.20.228 90:9A:4A:xx:xx:xx dhcp20-user bound 2m16s
11 D 10.0.30.252 D8:3A:DD:xx:xx:xx dhcp30-iot bound 7m1s
12 D 10.0.20.254 AA:04:14:xx:xx:xx dhcp20-user bound 4m49s
13 D 10.0.20.251 70:EE:50:xx:xx:xx dhcp20-user bound 6m6s
14 D 10.0.20.238 74:DF:BF:xx:xx:xx dhcp20-user bound 12m56s
15 D 10.0.20.235 5C:80:B6:xx:xx:xx dhcp20-user bound 3m44s
16 D 10.0.20.227 D0:17:C2:xx:xx:xx dhcp20-user bound 11m26s
17 D 10.0.20.226 D0:17:C2:xx:xx:xx dhcp20-user bound 6m51s
18 D 10.0.40.254 12:AB:66:xx:xx:xx dhcp40-guest bound 59s
Routing table:
/ip route print
Expected: Connected routes for 10.0.20.0/24, 10.0.30.0/24, 10.0.40.0/24 present.
[admin@MikroTik] > /ip route print
Flags: D - DYNAMIC; I - INACTIVE, A - ACTIVE; c - CONNECT, d - DHCP
Columns: DST-ADDRESS, GATEWAY, ROUTING-TABLE, DISTANCE
DST-ADDRESS GATEWAY ROUTING-TABLE DISTANCE
DAd 0.0.0.0/0 192.168.1.1 main 1
DAc 10.0.20.0/24 vlan20-user main 0
DAc 10.0.30.0/24 vlan30-iot main 0
DAc 10.0.40.0/24 vlan40-guest main 0
DAc 192.168.1.0/24 ether1 main 0
DIc 192.168.88.0/24 ether5 main 0
Phase 2 — TP-Link Switch
The PVID Problem
Configuring the switch proved to be the difficult task as well. The symptom was specific: WiFi devices (connected via the APs on the trunk ports) received correct IPs, but wired devices on ports 2–5 did not get any IP at all.
The root cause was PVID misconfiguration. PVID determines which VLAN the switch assigns to an untagged incoming frame. The ports had been added to VLAN 20 as untagged members, but their PVID was still set to 1 (the default), and they were still listed as members of VLAN 1.
| State | PVID | VLAN 1 membership | VLAN 20 membership | Result |
|---|---|---|---|---|
| Broken | 1 | Untagged | Untagged | Conflict / dropped |
| Broken | 1 | Not Member | Untagged | PVID points nowhere |
| Correct | 20 | Not Member | Untagged | Traffic reaches VLAN 20 |
The fix for ports 2–5:
- Set PVID to
20inVLAN → 802.1Q VLAN → Port Configfor ports 2, 4, 5 - Set PVID to
30inVLAN → 802.1Q VLAN → Port Configfor port 3 - Set VLAN 1 membership to
Not Memberfor ports 2–5 - Set VLAN 20 membership to
Untaggedfor ports 2, 4, 5; - Set VLAN 30 membership to
Untaggedfor port 3
Final Port Configuration
| Port | Type | Connected To | VLANs | PVID |
|---|---|---|---|---|
| 1 | Trunk | MikroTik ether2 | 20, 30, 40 tagged | 1 |
| 2 | Access | Wired devices | 20 untagged | 20 |
| 3 | Access | Wired devices | 30 untagged | 30 |
| 4 | Access | Wired devices | 20 untagged | 20 |
| 5 | Access | Wired devices | 20 untagged | 20 |
| 6 | Trunk | EAP225 AP #1 | 20, 30, 40 tagged | 1 |
| 7 | Trunk | EAP225 AP #2 | 20, 30, 40 tagged | 1 |
| 8 | Future | Shuttle eth1 (pending) | — | 1 |
Port 8 is reserved as the mirror destination for the Shuttle IDS sensor. Port mirroring configuration will be covered in the next post once the Shuttle is physically connected.
Verification — Evidence of Completion
The following are the actual verification steps run after configuration. Each one maps to a success criterion defined at the start.
1. DHCP Assignment per VLAN
On a wired device connected to port 2:
ip addr show eth0
Expected: inet 10.0.20.xxx/24 brd 10.0.20.255 scope global dynamic eth0
ondrej@kali:~$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether d0:17:c2:xx:xx:xx brd ff:ff:ff:ff:ff:ff
inet 10.0.20.227/24 brd 10.0.20.255 scope global dynamic noprefixroute eth0
valid_lft 1293sec preferred_lft 1293sec
inet 10.0.20.226/24 brd 10.0.20.255 scope global secondary dynamic eth0
valid_lft 1368sec preferred_lft 1368sec
inet6 fdaf:cde6:48f8:46ef:f541:f563:5bce:2975/64 scope global dynamic noprefixroute
valid_lft 1730sec preferred_lft 1730sec
inet6 fe80::2ec1:1405:4e8b:a28d/64 scope link noprefixroute
valid_lft forever preferred_lft forever
2. Gateway Reachability
ping -c 4 10.0.20.1 # VLAN 20 gateway
Expected: 0% packet loss, rtt < 5ms.
ondrej@kali:~$ ping -c 4 10.0.20.1
PING 10.0.20.1 (10.0.20.1) 56(84) bytes of data.
64 bytes from 10.0.20.1: icmp_seq=1 ttl=64 time=0.364 ms
64 bytes from 10.0.20.1: icmp_seq=2 ttl=64 time=0.320 ms
64 bytes from 10.0.20.1: icmp_seq=3 ttl=64 time=0.333 ms
64 bytes from 10.0.20.1: icmp_seq=4 ttl=64 time=0.353 ms
--- 10.0.20.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3050ms
rtt min/avg/max/mdev = 0.320/0.342/0.364/0.017 ms
ping -c 4 10.0.30.1 # IoT gateway — inter-VLAN
ping -c 4 10.0.40.1 # Guest gateway — inter-VLAN
Expected: Both succeed. Success here confirms inter-VLAN routing is active — traffic is crossing VLAN boundaries through the MikroTik, not just reaching a local gateway.
ondrej@kali:~$ ping -c 4 10.0.30.1
PING 10.0.30.1 (10.0.30.1) 56(84) bytes of data.
64 bytes from 10.0.30.1: icmp_seq=1 ttl=64 time=0.304 ms
64 bytes from 10.0.30.1: icmp_seq=2 ttl=64 time=0.316 ms
64 bytes from 10.0.30.1: icmp_seq=3 ttl=64 time=0.349 ms
64 bytes from 10.0.30.1: icmp_seq=4 ttl=64 time=0.349 ms
--- 10.0.30.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3080ms
rtt min/avg/max/mdev = 0.304/0.329/0.349/0.019 ms
ondrej@kali:~$ ping -c 4 10.0.40.1
PING 10.0.40.1 (10.0.40.1) 56(84) bytes of data.
64 bytes from 10.0.40.1: icmp_seq=1 ttl=64 time=0.256 ms
64 bytes from 10.0.40.1: icmp_seq=2 ttl=64 time=0.306 ms
64 bytes from 10.0.40.1: icmp_seq=3 ttl=64 time=0.286 ms
64 bytes from 10.0.40.1: icmp_seq=4 ttl=64 time=0.268 ms
--- 10.0.40.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3055ms
rtt min/avg/max/mdev = 0.256/0.279/0.306/0.018 ms
3. Internet Access
Repeat from a device on each VLAN (20, 30, 40):
ping -c 4 8.8.8.8
ping -c 4 google.com
Expected: 0% packet loss on both. google.com resolving also confirms DNS is working
through the MikroTik.
ondrej@kali:~$ ping -c 4 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=116 time=3.31 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=116 time=3.49 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=116 time=3.74 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=116 time=3.46 ms
--- 8.8.8.8 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3007ms
rtt min/avg/max/mdev = 3.311/3.501/3.744/0.155 ms
ondrej@kali:~$ ping -c 4 google.com
PING google.com (142.251.141.174) 56(84) bytes of data.
64 bytes from lcprga-ag-in-f14.1e100.net (142.251.141.174): icmp_seq=1 ttl=116 time=3.16 ms
64 bytes from lcprga-ag-in-f14.1e100.net (142.251.141.174): icmp_seq=2 ttl=116 time=3.51 ms
64 bytes from lcprga-ag-in-f14.1e100.net (142.251.141.174): icmp_seq=3 ttl=116 time=3.63 ms
64 bytes from lcprga-ag-in-f14.1e100.net (142.251.141.174): icmp_seq=4 ttl=116 time=3.54 ms
--- google.com ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 3.159/3.459/3.625/0.178 ms
4. VLAN Isolation — Negative Test (Baseline)
From a device on VLAN 30 (IoT), ping a device on VLAN 20 (User):
At this stage, without firewall rules, this should succeed — inter-VLAN routing is intentionally open. This output is the baseline. When firewall rules are added in the next phase, re-running this test should fail, giving a concrete before/after diff.
5. Traceroute — Routing Path Confirmation
Traceroute to confirm traffic is routed through the MikroTik and not bridged:
traceroute 10.0.30.<device-ip>
Expected first hop: 10.0.20.1 — the User VLAN gateway. If the first hop is the
destination device directly, there is VLAN leakage.
ondrej@kali:~$ traceroute 10.0.30.252
traceroute to 10.0.30.252 (10.0.30.252), 30 hops max, 60 byte packets
1 10.0.20.1 (10.0.20.1) 0.277 ms 0.136 ms 0.195 ms
2 10.0.30.252 (10.0.30.252) 0.322 ms 0.286 ms 0.224 ms
Additional Evidence Methods
Beyond the CLI verification above, the following approaches strengthen the evidence trail for this build and are worth adding to the post or as linked artifacts.
Network Scan Before / After
Run from a device on VLAN 20 before firewall rules are applied. Save output — this is the before-baseline to diff against after the next phase.
sudo nmap -sn 10.0.30.0/24
sudo nmap -sn 10.0.40.0/24
ondrej@kali:~$ sudo nmap -sn 10.0.30.0/24
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-18 17:10 CET
Nmap scan report for 10.0.30.1
Host is up (0.00030s latency).
Nmap scan report for 10.0.30.251
Host is up (0.038s latency).
Nmap scan report for 10.0.30.252
Host is up (0.00028s latency).
Nmap scan report for 10.0.30.253
Host is up (0.00031s latency).
Nmap done: 256 IP addresses (4 hosts up) scanned in 4.00 seconds
ondrej@kali:~$ sudo nmap -sn 10.0.40.0/24
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-18 17:10 CET
Nmap scan report for 10.0.40.1
Host is up (0.00030s latency).
Nmap done: 256 IP addresses (1 host up) scanned in 4.05 seconds
What Failed and Why
| Attempt | What I tried | What happened | Root cause |
|---|---|---|---|
| 1 | Enable VLAN filtering while ether5 in bridge | Lost management access | Bridge can’t be both tagged (routing) and accessible untagged |
| 2 | PVID left at 1 on access ports | Wired devices got no DHCP | PVID must match the untagged VLAN the port is a member of |
| 3 | Ports 2–5 left as VLAN 1 Untagged + VLAN 20 Untagged | Traffic confusion, wrong DHCP pool | A port can only be Untagged in one VLAN |
Current State and Next Steps
Status: Operational. All five success criteria met.
-
Firewall rules — block Guest and IoT from reaching User VLAN; restrict IoT to internet only. The negative test in criterion 4 above provides the baseline to diff against once rules are in place.
-
Management VLAN — create VLAN 10 for all infrastructure management (switch, APs, MikroTik), separate from user-facing VLANs.
- Pi-hole local DNS resolver — Pi-hole on VLAN 20 (
10.0.20.x) serving all VLANs. Adding this has several downstream consequences that will each need handling:- DHCP —
dns-serveron all three DHCP networks must be updated from the gateway IP to the Pi-hole IP - Firewall rules — DNS allow rules for VLANs 30 and 40 to reach Pi-hole on VLAN 20 must be inserted before the inter-VLAN deny rules, or IoT and Guest clients lose name resolution the moment firewall rules go live
- DNS enforcement — NAT redirect rule to catch clients bypassing Pi-hole with a
hardcoded upstream (e.g.
8.8.8.8) and redirect them silently to Pi-hole - Redundancy — Pi-hole becomes a single point of failure for DNS across all VLANs; a fallback or second instance needs to be planned
- IDS visibility — Pi-hole query logs per VLAN enable detection scenarios not
visible in packet capture alone: IoT beaconing to new domains, DNS tunnelling,
bypass attempts; cross-correlate with Zeek
dns.log - Per-VLAN policy — IoT group on strict allowlist (only known manufacturer domains), User and Guest on standard ad/tracking blocklists
- DHCP —
-
mDNS across VLANs — placing IoT devices on VLAN 30 breaks mDNS-dependent features (Chromecast, AirPlay, printers, smart home discovery) since mDNS is link-local and does not cross VLAN boundaries by design. Needs evaluation before moving devices: either accept the limitation, use an mDNS reflector/repeater (e.g. Avahi on the Shuttle or a dedicated service on the MikroTik) to selectively bridge mDNS between VLANs 20 and 30, or keep specific devices on VLAN 20 if discovery is essential. The mDNS reflector approach has security implications — it partially defeats the isolation goal — so the scope of what gets reflected needs to be intentional.
- Port mirroring + Shuttle connection — physically connect the Shuttle and configure
port mirroring on the switch (source ports 1, 6, 7 → destination port 8, both
directions). Evidence plan for that post:
tcpdump -i eth1 -nn -c 20to confirm frames arrive on the sensor interfacetcpdump -i eth1 -nn -e vlanto confirm VLAN tags are preserved through the mirror- A short
.pcapopened in Wireshark — Protocol Hierarchy screenshot and per-VLAN filter as publishable artifacts
-
Zeek deployment — configure on Shuttle
eth1, validateconn.logshows entries from all three VLAN subnets. - Alert on inter-VLAN anomalies — first detection use case: IoT device initiating connections to User VLAN addresses.