This repository was archived by the owner on Sep 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathindex.html
1663 lines (1161 loc) · 129 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<title>OpenBSD Router Guide</title>
<meta charset="utf-8">
<meta name="license" content="https://creativecommons.org/licenses/by-sa/4.0/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" type="text/css" href="/includes/css/stylesheet.css">
<link rel="shortcut icon" href="/includes/img/openbsd-favicon.ico" type="image/x-icon">
</head>
<body>
<article>
<table>
<tr>
<td><img src="/includes/img/openbsd-icon.webp" alt="OpenBSD icon"></td>
<td>
<h1 class="title">OpenBSD Router Guide</h1>
<h4>Network segmenting firewall, DHCP, DNS with Unbound, domain blocking and much more<br>
<span style="font-size:x-small;font-weight:initial;">OpenBSD: 7.5 · Published: 2020-11-05 · Updated: 2024-04-05 · Version: 2.1.9</span>
</h4>
</td>
</tr>
</table>
<h2>Introduction</h2>
<div class="info info-yellow abstract">In this guide we're going to take a look at how we can use cheap and "low end" hardware to build an amazing OpenBSD router with firewalling capabilities, segmented local area networks, DNS with domain blocking, DHCP and more.<br><br>We will use a setup in which the router segments the local area network (LAN) into three separate networks, one for the grown-ups in the house, one for the children, and one for public facing servers (a <a href="https://en.wikipedia.org/wiki/DMZ_%28computing%29">DMZ</a>), such as a private web server or mail server. We will also look at how we can use DNS to block out ads, porn, and other websites on the Internet. The OpenBSD router can also be used on small to mid-size offices.</div>
<p style="margin-top:30px;font-size:larger;">Table of contents</p>
<ul>
<li><a href="#typographical-conventions">Typographical conventions used in this guide</a></li>
<li><a href="#why-a-firewall">Why a firewall?</a></li>
<li><a href="#the-hardware">The hardware</a></li>
<li><a href="#why-openbsd">Why OpenBSD?</a></li>
<li><a href="#the-network">The network</a>
<ul>
<li><a href="#setting-up-the-network">Setting up the network</a></li>
</ul>
</li>
<li><a href="#dhcp">DHCP</a></li>
<li><a href="#a-packet-filtering-firewall">PF - A packet filtering firewall</a>
<ul>
<li><a href="#pf-setup">PF setup</a></li>
<li><a href="#clarifications">Clarifications</a></li>
<li><a href="#pf-domain-name-resolution">Domain name or hostname resolution</a></li>
<li><a href="#the-ruleset">The ruleset</a>
<ul>
<li><a href="#passlist">The children's pass list</a>
<ul>
<li><a href="#persistent-table">Using a persistent table</a></li>
</ul>
</li>
</ul>
</li>
<li><a href="#loading-ruleset">Loading the rules</a></li>
<li><a href="#logging">Logging and monitoring</a></li>
<li><a href="#pf-dhcp">Don't try to block DHCP</a></li>
</ul>
</li>
<li><a href="#domain-name-service">DNS</a>
<ul>
<li><a href="#unbound">I present to you, Unbound</a></li>
<li><a href="#blocking-with-dns">Blocking with DNS</a>
<ul>
<li><a href="#nxdomain">NXDOMAIN vs redirecting</a></li>
</ul>
</li>
<li><a href="#doh">The problem with DNS over HTTPS (DoH)</a></li>
<li><a href="#unbound-setup">Setting up Unbound</a>
<ul>
<li><a href="#basic-settings">Basic settings</a></li>
<li><a href="#lets-block-some-domains">Let's block some domains!</a></li>
</ul>
</li>
<li><a href="#dns-security">DNS security</a>
<ul>
<li><a href="#dns-hijacking">DNS hijacking</a>
<ul>
<li><a href="#dns-hijacking-prevention">DNS hijacking prevention</a></li>
</ul>
</li>
<li><a href="#dns-spoofing">DNS spoofing</a>
<ul>
<li><a href="#dns-spoofing-prevention">DNS spoofing prevention</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><a href="#appendix">Appendix</a>
<ul>
<li><a href="#inspecting-doh">Inspecting DNS over HTTPS (DoH)</a></li>
<li><a href="#blocking-doh">Blocking DNS over HTTPS (DoH)</a></li>
<li><a href="#dhcp-domain">Adding the domain-name option to DHCP and using a FQDN</a></li>
<li><a href="#pf-badhost">Adding pf-badhost</a></li>
<li><a href="#recommended-reading">Recommended reading</a></li>
<li><a href="#relevant-links">Relevant links</a></li>
<li><a href="#how-to-contribute">How to contribute to the guide?</a></li>
</ul>
</li>
</ul>
<h2 id="typographical-conventions">Typographical conventions used in this guide</h2>
<ul>
<li><code>Fixed-width</code> (mono-spaced) font is used for terminal commands, file names and paths, configuration parameters, etc.</li>
<li>Terminal commands that must be typed as the <code>root</code> user is prefixed with the <code>#</code> pound sign and the command is in <b>bold</b> text.</li>
<li>Terminal commands that can be typed as the regular user is prefixed with the <code>$</code> dollar sign and the command is in <b>bold</b> text.</li>
</ul>
<h2 id="why-a-firewall">Why a firewall?</h2>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Currently this guide only deals with IPv4 as most people still don't use IPv6 and many ISPs also still only use IPv4, but IPv6 is planned for a future update of the guide.</p>
<p>Almost no matter how you connect to the Internet from your home or office, you need a real firewall between you and the modem or router that your ISP has provided you with.</p>
<p>Very rarely do consumer-grade modems or routers get firmware updates and they are often vulnerable to <a href="https://en.wikipedia.org/wiki/Home_router#Security">network attacks</a> that turns these devices into <a href="https://en.wikipedia.org/wiki/Botnet">botnets</a>, such like the <a href="https://en.wikipedia.org/wiki/Mirai_(malware)">Mirai malware</a>. Many consumer-grade modems and routers is to blame for some of the largest <a href="https://en.wikipedia.org/wiki/Distributed_denial_of_service_attack">distributed denial of service (DDoS) attacks</a>.</p>
<p>A firewall between you and your ISP modem or router cannot protect your modem or router device against attacks, but it can protect your computers and devices on the inside of the network, and it can help you monitor and control the traffic that comes and goes to and from your local network.</p>
<p>Without a firewall between your local network and the ISP modem or router you could basically consider this an open door policy, like leaving the door to your house wide open, because you cannot trust the equipment from your ISP.</p>
<p>It is always a really good idea to put a real firewall between your local network and the Internet, and with OpenBSD you get an very solid solution.</p>
<h2 id="the-hardware">The hardware</h2>
<p>You don't have to buy expensive hardware to get an effective router and firewall for your house or office. Even with cheap and "low end" hardware you can get a very solid solution.</p>
<p>I have build multiple solutions with the <a href="https://www.asrock.com/mb/Intel/Q1900DC-ITX/">ASRock Q1900DC-ITX</a> motherboard that comes with an Intel Quad-Core Celeron processor.</p>
<p><img src="/includes/img/asrock-q1900dc-itx.webp" alt="ASRock Q1900DC-ITX motherboard"></p>
<p>I'll admit, it's a pretty "crappy" motherboard, but it gets the job done and I have several builds that have run very solid for many years on gigabit networks with full saturation and the firewall, DNS, etc. working "overtime" and the CPU hardly breaks a sweat.</p>
<p>The ASRock Q1900DC-ITX motherboard has the advantage that it comes with a DC-In Jack that is compatible with a 9~19V power adapter, making it very power saving. Unfortunately the ASRock Q1900DC-ITX motherboard is no longer made, but I'm just using it as an example, I have used several other cheap boards as well.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Many other low power brands from other motherboard producers can be uses as well, such as the famous <a href="https://www.pcengines.ch/apu2.htm">APU2</a> from PC Engines. Another option is one of the Mini PC's from <a href="https://www.qotom.net/product/">Qotom</a> or <a href="https://www.jetwaycomputer.com/Intel.html">Jetway</a>.</p>
<p>I have also used the ASRock Q1900-ITX (it doesn't come with the DC-In Jack) combined with a PicoPSU.</p>
<p><img src="/includes/img/picopsu.webp" alt="PicoPSU power supply"></p>
<p>You can find different brands and versions of the PicoPSU, some are better quality than others. I have two different brands, the original and a cheaper knockoff, both performs very well and they save quite a bit of power contrary to running with a normal power supply.</p>
<p>Last, I am using a cheap Intel knockoff quad port NIC found on Ebay like this one:</p>
<p><img src="/includes/img/intel-quad-nic.webp" alt="Intel Quad NIC"></p>
<p>I know it is better to use quality hardware, especially on a network that you care about, but this tutorial is about how you can get away with using fairly cheep hardware and still get an extremely useful product that will continue to serve you well for many years - at least that is my experience.</p>
<p>I recommend that you look for a low power mini ITX board with hardware <a href="https://www.openbsd.org/amd64.html">supported by OpenBSD</a>, such as an Intel Celeron or Intel i3 processor. These boards are typically cheap, less power hungry, and they don't take up much space. I don't recommend using the Intel Atom CPU if you have a gigabit network as they usually choke because they can't handle the amount of traffic, but your mileage may vary.</p>
<p>You might also need a couple of cheap gigabit switches for the segmented local network, at least if you have more than one computer you want to connect to the same LAN :)</p>
<h2 id="why-openbsd">Why OpenBSD?</h2>
<p>In truth, you can get a somewhat similar setup with one of the other <a href="https://en.wikipedia.org/wiki/Comparison_of_BSD_operating_systems">BSD flavors</a> or one of the many different <a href="https://en.wikipedia.org/wiki/Linux_distribution">Linux distribution</a>, but <a href="https://www.openbsd.org/">OpenBSD</a> is specifically very well suited and designed for this kind of task. Not only does it come with all the needed software in the base install, but it also has significantly better security and tons of improved mitigations already build-in into the operating system. I <a href="https://www.unixdigest.com/articles/openbsd-is-fantastic.html">highly recommend</a> OpenBSD over any other operating system for this kind of task.</p>
<p>Furthermore, OpenBSD is special, and this is not an exaggeration. The manual pages are very readable and often the only information you need to, more or less, effortless create configuration files for the various services you need. The OpenBSD project has a very high level of quality requirements for both the software and the manual pages.</p>
<p>This guide is not going to show you how to install OpenBSD. If you haven't done that before I recommend you spin up some kind of virtual machine or see if you have some unused and supported hardware laying around you can play with. OpenBSD is one of the easiest and quickest operating systems to install. Don't be afraid of the non-gui approach, once you have tried it you will really appreciate the simplicity. Use the default settings when in doubt.</p>
<p>Before you endeavor on this journey make sure to reference the OpenBSD documentation! Not only is everything very well documented, but you will most likely find all the answers you need right there. Read the <a href="https://www.openbsd.org/faq/index.html">OpenBSD FAQ</a> and take a look at the different <a href="https://man.openbsd.org/">manual pages</a> for the software we're going to use.</p>
<p>Another really useful place to find general information about OpenBSD is the <a href="https://marc.info/?l=openbsd-misc">OpenBSD mailing list archives</a>. Also make sure to stay up to date with relevant information by subscribing to the <a href="https://www.openbsd.org/mail.html">Announcements and security advisories</a> mailing list.</p>
<p class="info info-green" style="font-size:initial;"><b>TIP:</b> Please consider <a href="https://www.openbsd.org/donations.html">supporting OpenBSD</a>! Even if you don't use OpenBSD on a daily basis, but perhaps make use of <a href="https://www.openssh.com/">OpenSSH</a> on Linux, then you're really using software from the OpenBSD project. Consider making a small, but steady donation to support the further development of all the great software the OpenBSD developers make!</p>
<h2 id="the-network">The network</h2>
<p>A router is basically a device that regulate network traffic between two or more separate networks. The router will ensure that network traffic intended for the local network doesn't run out into the wild on the Internet, and traffic on the Internet, that is not intended for your local network, stays on the Internet.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> A router is sometimes also referred to as a gateway, which generally is alright, but in truth a real gateway joins dissimilar systems, while a router joins similar networks. An example of a gateway would be a device that joins a computer network with a telecommunications network.</p>
<p>In this tutorial we're building a router and we have 4 networks of the same type to work with. One is the Internet and the other three are the internally segmented local area networks (LANs). Some people prefer to work with virtual LANs, but in this tutorial we're going to use the quad port NIC from the illustration above. You can achieve the same result by using multiple one port NICs if you prefer that, you just have to make sure that you have enough room and free PCI slots on the motherboard. You can also use the Ethernet port on the motherboard itself, but it depends on the driver and support for the device. I have had no problems using the Realtek PCI gigabit Ethernet controller that normally comes with many motherboards even though I recommend Intel over Realtek.</p>
<p>Of course you don't have to segment the network into several parts if you don't need that, and it will be very easy to change the settings from this guide, but I have decided to use this approach in order to show you how you can protect your children by segmenting their network into a separate LAN that not only gets ad and porn blocking using DNS blocking (all the segments gets that), but you can even make a pass list that passes only the parts of the Internet you want them to have access to. The last part about a pass list is difficult and generally not recommended unless your children requires only very limited access, but it is doable with some work, and the guide is going to show you one way you can do that.</p>
<p>This is an illustration of the network we're going to setup:</p>
<pre class="no-style">
Internet
|
xxx.xxx.xxx.xxx
ISP Modem (WAN)
10.24.0.23
|
OpenBSD
10.24.0.50
(router/firewall)
|
┌────────────────────+────────────────────┐
| | |
NIC1 NIC2 NIC3 (DMZ)
192.168.1.1 192.168.2.1 192.168.3.1
LAN1 switch LAN2 switch LAN3 switch
| | |
└─ 192.168.1.x ├─ 192.168.2.x └─ 192.168.3.2
Grown-up PC | Child PC1 Public web server
|
└─ 192.168.2.x
Child PC2
</pre>
<p>The IP addresses that begins with 10.24.0 are whatever IP addresses your ISP router or modem gives you, it may be something very different. The IP addresses beginning with 192.168 are the IP addresses that we're going to use in the guide for our local area network (LAN).</p>
<p>The guide does not deal with any kind of wireless connectivity. Wireless chip firmware is notoriously buggy and exploitable and I recommend you don't use any kind of wireless connectivity, if you can do without. If you do require wireless connectivity I strongly recommend that you disable wireless access from the ISP modem or router completely (if possible), and then buy the best wireless router you can find and put it behind the firewall in an isolated segment instead. That way should your wireless device ever be compromised you can better control the outcome and limit the damage. You can further setup the wireless router such that any devices connected to it have their own IPs that pass directly through the wireless router, but at the same time block traffic directly originating from the wireless router itself. That way you can prevent the wireless router from "phoning home". You can also get a wireless adapter supported by OpenBSD and have your OpenBSD router run as the actual access point, however I much prefer to segment the wireless part to either a separate wireless router or another OpenBSD machine serving as a wireless access point behind the firewall itself.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> At present, as far as I know, none of the OpenBSD wireless drivers are fully without problems yet.</p>
<h3 id="setting-up-the-network">Setting up the network</h3>
<p>The first thing we'll setup is the different NICs on our OpenBSD router. On my particular machine I have disabled the NIC that is build into the motherboard via the BIOS and I am only going to use the four port Intel knockoff NIC.</p>
<p>If you're following this tutorial and only want a basic firewall then you need at least two separate NICs.</p>
<p>Before we begin make sure you have read and understood the different options in <a href="https://man.openbsd.org/hostname.if">hostname.if</a> man page. Also take a look at the networking section in the <a href="https://www.openbsd.org/faq/faq6.html">OpenBSD FAQ</a>.</p>
<p>Since I am using Intel the <a href="https://man.openbsd.org/em">em</a> driver is the one OpenBSD loads and each port on the NIC is listed as a separate card. This means that each card is listed with <code>emX</code> where X is the actual number of the port on the given card.</p>
<p>A <code>dmesg</code> lists my NIC with the four ports like this:</p>
<pre><b># dmesg</b>
em0 at pci2 dev 0 function 0 "Intel I350" rev 0x01: msi, address a0:36:9f:a1:66:b8
em1 at pci2 dev 0 function 1 "Intel I350" rev 0x01: msi, address a0:36:9f:a1:66:b9
em2 at pci2 dev 0 function 2 "Intel I350" rev 0x01: msi, address a0:36:9f:a1:66:ba
em3 at pci2 dev 0 function 3 "Intel I350" rev 0x01: msi, address a0:36:9f:a1:66:bb</pre>
<p>This shows that my card is recognized as an Intel I350-T4 PCI Express Quad Port Gigabit NIC.</p>
<p>The next thing is to figure out which port that physically matches the number listed above. You can do that by manually plugging in an Ethernet wire, coming from an active (turned on) switch, modem or router, into each port, one at a time, in order to see which port gets activated and then note that down somewhere.</p>
<p>You can check the activity status with the <code>ifconfig</code> command. A port without the Ethernet cable will be listed as <code>no carrier</code> in the <code>status</code> field, whereas the port with the cable attached will be listed as <code>active</code>. Like this:</p>
<pre><b># ifconfig</b>
em1: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr a0:36:9f:a1:66:b9
index 2 priority 0 llprio 3
media: Ethernet autoselect (none)
<b>status: active</b>
em2: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
lladdr a0:36:9f:a1:66:ba
index 3 priority 0 llprio 3
media: Ethernet autoselect (none)
<b>status: no carrier</b></pre>
<p>We're going to use the <code>em0</code> port as the one we connect to the modem or router from the ISP, i.e. the Internet. In my specific case I have a public IP address from my ISP, and you're going to need that if you want to run something like a web server from your home, but in case you don't need that you can setup the card with DHCP.</p>
<p>In my case I need to put in a specific fixed IP address for <code>em0</code> which then gets traffic forwarded by my ISP from my public IP. To do that I set the <code>em0</code> card with the following information:</p>
<pre><b># echo 'inet 10.24.0.50 255.255.254.0 NONE' > /etc/hostname.em0</b></pre>
<p>If you don't need a public IP address and you get your IP from your ISP via DHCP, then just enter <code>dhcp</code> instead:</p>
<pre><b># echo 'dhcp' > /etc/hostname.em0</b></pre>
<p>Then I'll set the rest of the NIC ports up with the IP addresses I have previously illustrated.</p>
<pre># <b>echo 'inet 192.168.1.1 255.255.255.0 NONE' > /etc/hostname.em1</b>
<b># echo 'inet 192.168.2.1 255.255.255.0 NONE' > /etc/hostname.em2</b>
<b># echo 'inet 192.168.3.1 255.255.255.0 NONE' > /etc/hostname.em3</b></pre>
<p>Take a look at <a href="https://man.openbsd.org/hostname.if">hostname.if</a> for more information.</p>
<p>Then I need to setup the IP of the ISP gateway. Depending on the setup of your ISP this might be another IP address than the one from the ISP modem or router. If you don't add the <code>/etc/mygate</code> then no default gateway is added to the <a href="https://en.wikipedia.org/wiki/Routing_table">routing table</a>. You don't need the <code>/etc/mygate</code> if you get your IP from your ISP modem or router via DHCP. If you use the <code>dhcp</code> directive in any <code>hostname.ifX</code> then the entries in <code>/etc/mygate</code> will be ignored. This is because the card that get its IP address from a DHCP server will also get gateway routing information supplied.</p>
<p>Last, but not least, we need to enable IP forwarding. IP forwarding is the process that enables IP packets to travel between network interfaces on the router. By default OpenBSD will not forward IP packets between various network interfaces. In other words, routing functions (also known as gateway functions) are disabled.</p>
<p>We can enable IP forwarding using the following commands:</p>
<pre># <b>sysctl net.inet.ip.forwarding=1</b>
# <b>echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf</b></pre>
<p>Now OpenBSD will be able to forward IPv4 packets from one NIC to another. Or, as in our specific case with the four port NIC, from one port to another. Take a look at the man page if you need IPv6.</p>
<h2 id="dhcp">DHCP</h2>
<p>Now we're ready to setup the <a href="https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol">Dynamic Host Configuration Protocol (DHCP)</a> service we will be running for our different PCs and devices attached to the different LANs. Before we begin make sure you have read and understood the different options in the <a href="https://man.openbsd.org/dhcpd.conf">dhcpd.conf</a> man page. Also take a look at the <a href="https://man.openbsd.org/dhcp-options">dhcp-options</a> man page for options that dhcpd supports.</p>
<p>We have the option to bind specific IP addresses to specific computers or devices that connect to our different LAN ports. This is needed if we want to forward any traffic from the Internet to something like a web server. We can bind a specific IP address to a specific computer via the <a href="https://en.wikipedia.org/wiki/MAC_address">MAC address</a> on the NIC of the relevant machine.</p>
<p>In this case I'll reserve all IP addresses ranging from 10 to 254 for the DHCP, while I'll leave the few left overs for any possible fixed addresses I might need.</p>
<p>Edit <code>/etc/dhcpd.conf</code> with your favorite text editor and set it up to suit your needs.</p>
<pre>subnet 192.168.1.0 netmask 255.255.255.0 {
option domain-name-servers 192.168.1.1;
option routers 192.168.1.1;
range 192.168.1.10 192.168.1.254;
}
subnet 192.168.2.0 netmask 255.255.255.0 {
option domain-name-servers 192.168.2.1;
option routers 192.168.2.1;
range 192.168.2.10 192.168.2.254;
}
subnet 192.168.3.0 netmask 255.255.255.0 {
option domain-name-servers 192.168.3.1;
option routers 192.168.3.1;
range 192.168.3.10 192.168.3.254;
host web.example.com {
fixed-address 191.168.3.2;
hardware ethernet 61:20:42:39:61:AF;
option host-name "webserver";
}
}</pre>
<p>The <code>option domain-name-servers</code> line specifies the DNS server we will be running on our router.</p>
<p>Also the computer serving as our web server on the public LAN has gotten a fixed IP address and provided a fixed hostname.</p>
<p>Also, if you don't want to segment the network into the different parts, but only want to have one LAN then you can just leave out the other subnets so you just have this:</p>
<pre>subnet 192.168.1.0 netmask 255.255.255.0 {
option domain-name-servers 192.168.1.1;
option routers 192.168.1.1;
range 192.168.1.10 192.168.1.254;
}</pre>
<p>Then we just need to make sure we enable and start the <code>dhcpd</code> service:</p>
<pre># <b>rcctl enable dhcpd</b>
# <b>rcctl start dhcpd</b></pre>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Take a look at the <a href="#dhcp-domain">Adding the domain-name option to DHCP and using a FQDN</a> in the appendix for information on how to easily add a <a href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name">fully qualified domain name (FQDN)</a> to your setup and how you can use the <code>domain-name</code> option in DHCP to avoid having to type the FQDN each time you need it. The section will also show you how you can avoid having to remember IP addresses if your LAN has multiple computers or devices attached.</p>
<h2 id="a-packet-filtering-firewall">PF - A packet filtering firewall</h2>
<p>A packet-filtering firewall examines each packet that crosses the firewall and decides whether to accept or deny individual packets, based on examining fields in the packet's IP and protocol headers, according to the set of rules that you specify.</p>
<p>Packet filters work by inspecting the source and destination IP and port addresses contained in each Transmission Control Protocol/Internet Protocol (TCP/IP) packet. TCP/IP ports are numbers that are assigned to specific services that identify which service each packet is intended for.</p>
<p>A common weakness in simple packet filtering firewalls is that the firewall examines each packet in isolation without considering what packets have gone through the firewall before and what packets may follow. This is called a "stateless" firewall. Exploiting a stateless packet filter is fairly easy. PF from OpenBSD is <b>not</b> a stateless firewall, it is a <a href="https://en.wikipedia.org/wiki/Stateful_firewall">stateful firewall</a>.</p>
<p>A stateful firewall keeps track of open connections and only allows traffic that either matches an existing connection or opens a new allowed connection. When state is specified on a matching rule the firewall dynamically generates internal rules for each anticipated packet being exchanged during the session. It has sufficient matching capabilities to determine if a packet is valid for a session. Any packets that do not properly fit the session template are automatically rejected.</p>
<p>One advantage of stateful filtering is that it is very fast. It allows you to focus on blocking or passing new sessions. If a new session is passed, all its subsequent packets are allowed automatically and any impostor packets are automatically rejected. If a new session is blocked, none of its subsequent packets are allowed. Stateful filtering also provides advanced matching abilities capable of defending against the flood of different attack methods employed by attackers.</p>
<p>Network Address Translation (NAT) enables the private network behind the firewall to share a single public IP address. NAT allows each computer in the private network to have Internet access, without the need for multiple Internet accounts or multiple public IP addresses. NAT will automatically translate the private network IP address for computers or devices on the network to the single public IP address as packets exit the firewall bound for the Internet. NAT also performs the reverse translation for returning packets. With NAT you can redirect specific traffic, usually determined by port number or a range of port numbers, coming in on your public IP address from the Internet to a specific server or servers located somewhere in your local network.</p>
<p><a href="https://man.openbsd.org/pf">Packet Filter (PF)</a> is OpenBSD's firewall system for filtering TCP/IP traffic and doing NAT. PF is also capable of normalizing and conditioning TCP/IP traffic, as well as providing bandwidth control and packet prioritization.</p>
<p>PF is actively maintained and developed by the entire OpenBSD team.</p>
<h2 id="pf-setup">PF setup</h2>
<p>Before we begin I assume that you have read both the <a href="https://www.openbsd.org/faq/pf/index.html">PF - User's guide</a> and the <a href="https://man.openbsd.org/pf.conf">pf.conf</a> man page, especially the man page is very important. Even if you don't understand all the different options make sure you read the documentation! For a complete and in-depth view of what PF can do, take a look at the <a href="https://man.openbsd.org/pf">pf</a> man page.</p>
<p>Also, let me start by saying that even though the syntax for PF is very readable, it is <strong>very easy</strong> to make mistakes when writing firewall rules. Even senior and experienced system administrators makes mistakes when writing firewall rules.</p>
<p>Writing firewall rules requires that you carefully plan out your goals, understand how to implement the different rules in order to achieve the desired results, and at the same time take your precautions against doing it wrong and accidentally logging yourself out :) I think we've all done that at one time or another, whether in haste, tiredness, or just by mistake, I know I have several times.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Please note that I have done my best to keep things as simple as possible and to use lots of comments in order to explain what each rule does. At the same time I have tested each rule out and monitored the impact and generally done my best to avoid complications and mistakes.</p>
<p>The most important part is that you don't make any assumptions. Always test your rules thoroughly. If something isn't working, try to remove as much as possible from your rules so you're left with the very basic. Then introduce one rule at a time until you reach the point where a rule is causing problems. Then deal with the setup step by step.</p>
<p>The really difficult part is to remember how data packets arrive at one NIC and how they are then forwarded to a machine on another NIC, and then relating this "journey" correctly to the terms <b>pass in</b>, <b>pass out</b>, <b>block in</b>, <b>block out</b>, <b>from</b> and <b>to</b>. These terms often does not work exactly as we tend to think.</p>
<p>The way I try to think about it is more or less to actually imagine myself "flowing along with the data" and standing in a particular NIC port looking at data coming in and out. That way I remember to consider data "coming" from a specific device attached to a specific port going "in" or "out" etc. It sounds silly, but it has helped me more than once.</p>
<h3 id="clarifications">Clarifications</h3>
<p>I want to start by clarifying some of the common default settings and keywords in PF.</p>
<p>When we talk about traffic that we <b>pass in</b> or <b>pass out</b>, one good way to remember what we're dealing with is to think in terms of data packets. We <b>pass in</b> data packets coming from computers to a NIC (the computers attached to that NIC) and we <b>pass out</b> data packets coming from the NIC to the computers attached to it.</p>
<p>The format is either that we then filter data packets on the destination:</p>
<pre><b>from</b> <i>source IP</i> <b>to</b> <i>destination IP</i> <b>[on]</b> <i> port</i></pre>
<p>Or we filter data packets on the source:</p>
<pre><b>from</b> <i>source IP</i> <b>[on]</b> <i>port</i> <b>to</b> <i>destination</i></pre>
<p>Please note that the <code>[on]</code> part is not part of the PF syntax.</p>
<ul style="list-style-type:none;">
<li><code>quick</code>
<ul style="list-style-type:none;">
<li><p>If a packet matches a <code>pass</code>, <code>block</code> or <code>match</code> rule, with the <code>quick</code> modifier, the packet <b>is passed without inspecting subsequent filter rules</b>. The rule with the <code>quick</code> modifier becomes the last matching rule.</p></li>
</ul>
</li>
<li><code>keep state</code>
<ul style="list-style-type:none;">
<li><p>You don't need to specify the <code>keep state</code> modifier for specific <code>pass</code> or <code>block</code> rules. The first time a packet matches a <code>pass</code> or <code>block</code> rule, <b>a state entry is created by default</b>.</p>
<p>Only if no rule matches a packet, the default action is <b>to pass the packet without creating a state</b>.</p></li>
</ul>
</li>
<li><code>on</code> interface/<code>any</code>
<ul style="list-style-type:none;">
<li><p>This rule applies only to packets <b>coming in on</b>, or <b>going out through</b>, this particular interface or interface group.</p>
<p>The <code>on any</code> modifier - will match any existing interface except loopback ones.</p></li>
</ul>
</li>
<li><code>inet</code>/<code>inet6</code>
<ul style="list-style-type:none;">
<li><p>The <code>inet</code> and <code>inet6</code> modifiers means that this rule applies only to packets <b>coming in on</b>, or <b>going out through</b>, this particular routing domain, meaning IPv4 or IPv6.</p>
<p>You can apply rules to specific routing domains without specifying the NIC. In that case the rule will match all traffic of that particular nature on all NICs. By specifying <code>inet</code> you explicitly address IPv4 traffic only.</p></li>
</ul>
</li>
<li><code>proto</code>
<ul style="list-style-type:none;">
<li><p>Protocol limiting is done using the <code>proto</code> modifier. A rule applies <b>only to packets of this protocol</b>, other protocols are not affected. You can lookup protocols in <code>/etc/protocols</code>. Common protocols are <a href="https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol">ICMP</a>, <a href="https://en.wikipedia.org/wiki/Transmission_Control_Protocol">TCP</a>, and <a href="https://en.wikipedia.org/wiki/User_Datagram_Protocol">UDP</a>.</p></li>
</ul>
</li>
<li><code>in</code> and <code>out</code>
<ul style="list-style-type:none;">
<li><p>This is one of the easiest parts of traffic direction to get wrong. A packet always <b>comes in on</b>, or <b>goes out through</b>, the Ethernet port on the Ethernet interface. <code>in</code> and <code>out</code> apply to incoming and outgoing packets through the physical Ethernet port where the Ethernet cable is attached. <b>If neither are specified, the rule will match packets in both directions.</b></p>
<p><code>in</code> and <code>out</code> is <b>never</b> used to deal with traffic going from one NIC to another NIC, that is done with network address translation (NAT), using the options <code>nat-to</code> and <code>rdr-to</code>. <code>in</code> and <code>out</code> only deals with traffic <b>in</b> and <b>out</b> from the physical Ethernet port on the card.</p></li>
</ul>
</li>
<li><code>from</code> and <code>to</code>
<ul style="list-style-type:none;">
<li><p>The <code>from</code> and <code>to</code> rule modifiers apply <b>only to packets with the specified source and destination addresses and ports</b>. Both the hostname or IP address, port, and OS specifications are optional.</p>
<p>When we're dealing with a router with multiple NICs it's easy to think like this: <i>I want to pass in packets from the external NIC (the NIC attached to the Internet) and then have them go to the first LAN NIC and from there out to a specific computer on that LAN</i>, meaning we follow the "trail of data" in our minds, and then write that out into something like this: <code>pass in on $ext_if from $ext_if to $dmz port 80</code>. But this will not make the HTTP traffic "magically" appear on port 80 on the LAN with a computer attached with a specific IP address. We would also require a specific <code>pass out</code> rule and furthermore need to determine exactly on which machine we want the data to end up. Unless you are really dealing with a very specific requirement, you never need such rules in your ruleset! The <a href="https://www.openbsd.org/faq/pf/filter.html#urpf">Unicast Reverse Path Forwarding (uRPF)</a> features of PF will protect your internal network very well and with a basic setup of correct network address translation (NAT), with the <code>nat-to</code> option, and redirection with the <code>rdr-to</code> option, PF will handle the packages from the inside to the outside and vice versa.</p>
<p>The <code>all</code> parameter is equivalent to writing <code>from any to any</code>. <b>Without explicitly declaring the direction, the default is</b> <code>from any to any</code>. This rule: <code>pass in on $dmz proto udp to port dns</code> translates into this: <code>pass in on em3 inet proto udp from any to any port = 53</code></p>
<p>There is also no need to use <code>to any port dns</code>, the <code>any</code> part is the default. You do however need the <code>to port dns</code></p></li>
</ul>
</li>
<li><code>nat-to</code> and <code>rdr-to</code>
<ul style="list-style-type:none;">
<li><p>Network address translation (NAT) options <b>modify either the source or destination address and port of the packets associated with a stateful connection</b>. PF modifies the specified address and/or port in the packet and recalculates IP, TCP, and UDP checksums as necessary.</p>
<p>A <code>nat-to</code> option specifies <b>that IP addresses are to be changed as the packet traverses the given interface</b>. This technique allows one or more IP addresses on the translating host (the OpenBSD router) to support network traffic for a larger range of machines on an <b>inside</b> network, i.e. a LAN.</p>
<p>The <code>nat-to</code> option is usually applied outbound, meaning <b>redirected from the inside network to the Internet</b>. <code>nat-to</code> to a local IP address <b>is not supported</b>.</p>
<p>The <code>rdr-to</code> option is usually applied inbound, meaning <b>redirected from the Internet into the inside network</b>.</p></li>
</ul>
</li>
<li>List items and range of addresses and ports
<ul style="list-style-type:none;">
<li><p>When you need to specify multiple items, e.g. multiple port numbers, you can separate them with a whitespace or a comma. Like this <code>port { 53 853 }</code> or like this <code>port { 53, 853 }</code></p>
<p>Ranges of addresses are specified using the <code>-</code> operator. e.g. <code>192.168.1.2 - 192.168.1.10</code> means all IP addresses from 192.168.1.2 until 192.168.1.10, both included.</p>
<p>Range of ports has multiple parameters, look at the man page for <a href="https://man.openbsd.org/pf.conf">pf.conf</a> and search for the text <q>Ports and ranges of ports are specified using these operators</q>.</p>
</li>
</ul>
</li>
</ul>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> Please note that each time a packet processed by the packet filter comes in on or goes out through a network interface, the filter rules are evaluated in sequential order, from first to last. For <code>block</code> and <code>pass</code>, <b>the last matching rule decides what action is taken</b>. If no rule matches the packet, the default action is to pass the packet without creating a state. For <code>match</code>, rules are evaluated <b>every time they match</b>.</p>
<h3 id="pf-domain-name-resolution">Domain name or hostname resolution</h3>
<p>If you decide to use hostnames and/or domain names in your PF setup you need to know that <b>all domain name and hostname resolution is done at ruleset load-time</b>. This means that when the IP address of a host or a domain name changes, the ruleset <b>must be reloaded for the change to be reflected in the kernel</b>. It is not such that each time a specific rule runs, that has a hostname or domain name listed, that PF will do a new DNS lookup for that particular hostname or domain name. DNS lookup only happens when the ruleset is loaded.</p>
<p>This also means that you must make sure that the DNS server you're using is up and running <b>before</b> PF is started, otherwise PF will fail at loading the ruleset because it cannot resolve the hostname or domain name.</p>
<p>On OpenBSD PF starts <b>before</b> Unbound or any other installed DNS server, which is the correct thing to do from a security perspective.</p>
<p>I advice that you avoid using hostnames or domain names when using PF rules and stick to IP addresses if possible. It is possible to use hostnames and domain names, but direct IP addressing is by far the easiest and safest.</p>
<h3 id="the-ruleset">The ruleset</h3>
<p>It is a good idea to test out your ruleset on a test machine. There is often more than one way to achieve the same result. In my humble opinion, the best way is the way that is most clear to you (i.e. easy to understand).</p>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> Never write new rulesets on a remote device you are actively logged into unless you really know what you're doing. Getting logged out of a remote machine is never any fun.</p>
<p>Try to figure out how you can keep your rules as clear and as short as possible, using default values whenever possible. Yet, don't be afraid to specify modifiers that makes the rules more clear to understand, even though they are identical to the default values. A default value might be <code>any to any</code>, and you can leave that out then, but it might be easier to understand a particular rule when it actually says <code>any to any</code> in the text of the configuration file.</p>
<p>You can always parse the ruleset and check for errors without it being deployed with the command <code>pfctl -nf /etc/pf.conf</code>. Once you have loaded a ruleset with the command <code>pfctl -f /etc/pf.conf</code> you can view how the ruleset has been translated by PF with the <code>pfctl -s rules</code> command, which I advice that you to use regularly.</p>
<p>I prefer to keep my rulesets organized with sections and comments so I'll do the same in this example.</p>
<p>Use your favorite text editor and open up the file <code>/etc/pf.conf</code>.</p>
<p>First we setup some macros to better remember what NICs we use for what. Using macros for the NICs also makes it easy to change the driver name of the card if we ever buy a new card, or multiple new cards.</p>
<pre>#---------------------------------#
# Macros
#---------------------------------#
ext_if="em0" # External NIC connected to the ISP modem (Internet).
g_lan="em1" # Grown-ups LAN.
c_lan="em2" # Children's LAN.
dmz="em3" # Public LAN (DMZ).</pre>
<p>Next we set up a table for non-routable IP address. We do that because a very common network misconfiguration is the kind that lets traffic with non-routable addresses out to the Internet. We will use the table in our ruleset to block any attempt to initiate contact to non-routable addresses through the routers external NIC.</p>
<pre>#---------------------------------#
# Tables
#---------------------------------#
# This is a table of non-routable private addresses.
table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16 \
172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3 \
192.168.0.0/16 198.18.0.0/15 198.51.100.0/24 \
203.0.113.0/24 }</pre>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> Please note that macros and tables always goes at the top of <code>/etc/pf.conf</code>.</p>
<p>Then we begin with a <b>default blocking policy</b> and setup a couple of protective features.</p>
<pre>#---------------------------------#
# Protect and block by default
#---------------------------------#
set skip on lo0
# Spoofing protection for all NICs.
block in from no-route
block in quick from urpf-failed
# Block non-routable private addresses.
# We use the "quick" parameter here to make this rule the last.
block in quick on $ext_if from <martians> to any
block return out quick on $ext_if from any to <martians>
# Default blocking all traffic in on all LAN NICs from any computer or device
# attached.
block return in on { $g_lan $c_lan $dmz }
# Default blocking all traffic in on the external NIC from the Internet/ISP,
# we'll log that too.
block drop in log on $ext_if
# Allow ICMP.
match in on $ext_if inet proto icmp icmp-type {echoreq } tag ICMP_IN
block drop in on $ext_if proto icmp
pass in proto icmp tagged ICMP_IN max-pkt-rate 100/10
# We need the router to have access to the Internet, so we'll default allow
# packets to pass out from our router through the external NIC to the Internet.
pass out inet from $ext_if</pre>
<p>The IP addresses in the <code>martians</code> macro constitutes the <a href="https://tools.ietf.org/html/rfc1918">RFC1918</a> addresses which are not to be used on the Internet. As these IP addresses doesn't belong on the Internet they are called "martians", as they might as well have come from Mars. These addresses are also called <a href="https://en.wikipedia.org/wiki/Bogon_filtering">bogons</a>. Traffic to and from such addresses is dropped on the routers external interface.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Even though we implicitly block the "martians" IP address in the <code>block drop in log on $ext_if</code> statement, which is blocking everything by default, we still block the "martians" IP addresses explicitly first. This is a best practice because even with a properly configured router to handle network address translation, we need to take precautions against misconfiguration by mistake. A common misconfiguration is to let traffic with non-routable addresses out to the Internet. Since traffic from non-routeable addresses can play a part in several DoS attack techniques and other issues, consider explicitly blocking traffic from non-routeable addresses from entering the network through the external interface a "best practice" from a security point of view.</p>
<p>In previous versions of this guide (before version 1.5.0) I used to have the <a href="https://man.openbsd.org/pf.conf#Scrub">scrub</a> statement present in the setup above, however after having consulted with <a href="http://henningbrauer.com/">Henning Brauer</a> from the OpenBSD team (thanks Henning!) and doing some further research, I have decided to remove it as it deals with very specific corner cases (please see the documentation). You only need the <code>scrub</code> rule if a host on your network generates fragmented packets with the "dont‑fragment" flag set. The default PF behavior without the <code>scrub</code> rule is better suited for general usage.</p>
<p>The OpenBSD <a href="https://www.openbsd.org/faq/pf/example1.html">FAQ</a> contains an example setup for a very basic router, with some specific values for <code>scrub</code>, but my recommendation is to only use <code>scrub</code> when you know for a fact that you need it. If you do need it, insert it into the configuration after the <code>set skip</code> rule for the loopback interface, like this:</p>
<pre>set skip on lo0
match in all scrub</pre>
<p>Then add whatever parameters to the <code>scrub</code> rule you need.</p>
<p>I also used to have the following <a href="https://man.openbsd.org/pf.conf#Blocking_Spoofed_Traffic">antispoof</a> rule present in the spoofing protection section:</p>
<pre>antispoof quick for { $g_lan $c_lan $dmz }</pre>
<p>I have since removed the <code>antispoof</code> rule since the <a href="https://www.openbsd.org/faq/pf/filter.html#urpf">Unicast Reverse Path Forwarding (uRPF)</a> feature of PF provides the same functionality as <a href="https://www.openbsd.org/faq/pf/filter.html#antispoof">antispoof</a> rules, and as such we don't need it any longer, instead we just use the <code>block in quick from urpf-failed</code> rule.</p>
<p>The following information about the <code>antispoof</code> modifier is kept for educational purposes.</p>
<p>Spoofing is when someone fakes an IP address. The <code>antispoof</code> modifier expands to a set of filter rules that will block all traffic with a source IP from the network (directly connected to the specified interface) from entering the system through any other interface. This is sometimes referred to as "bleeding over" or "bleeding through".</p>
<p>The above <code>antispoof</code> directive is translated by PF into the following:</p>
<pre>block drop in quick on ! em1 inet from 192.168.1.0/24 to any
block drop in quick inet from 192.168.1.1 to any
block drop in quick on ! em2 inet from 192.168.2.0/24 to any
block drop in quick inet from 192.168.2.1 to any
block drop in quick on ! em3 inet from 192.168.3.0/24 to any
block drop in quick inet from 192.168.3.1 to any</pre>
<p>If we take, e.g., the <code>em1</code> NIC rule <code>block drop in quick on ! em1 inet from 192.168.1.0/24 to any</code> then that means: <i>block any traffic from the network with IP addresses ranging from 192.168.1.1 to 192.168.1.255, that doesn't originate from the em1 NIC itself, and that is going anywhere</i>. Since the <code>em1</code> NIC is the NIC in charge of all IP addresses in that specific range, then no traffic with such an IP address should originate from any other NIC.</p>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> Usage of <code>antispoof</code> should be <b>restricted</b> to interfaces that have been assigned an IP address, meaning that if you have unused NICs, or ports on a NIC, make sure to assign an IP address to each or don't include these in the <code>antispoof</code> option.</p>
<p>As mentioned, I have removed the <code>antispoof</code> rule and we are instead using a strict uRPF check. When a packet is run through the uRPF check, the source IP address of the packet is looked up in the routing table. If the outbound interface is found in the routing table and the entry is the same as the interface that the packet just came in on, then the uRPF check passes. Otherwise it's possible that the packet has had its source address spoofed and it is blocked.</p>
<p>We are allowing <a href="https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol">ICMP</a> in our setup, even though some network administrators completely block ICMP. People mainly block ICMP completely because of unwarranted actions such as network discovery attacks, covert communication channels, <a href="https://en.wikipedia.org/wiki/Ping_sweep">ping sweep</a>, <a href="https://en.wikipedia.org/wiki/Ping_flood">ping flood</a>, <a href="https://en.wikipedia.org/wiki/ICMP_tunnel">ICMP tunneling</a> and <a href="https://en.wikipedia.org/wiki/ICMP_Redirect_Message#Redirect">ICMP redirecting</a>. However, ICMP is much more than answering pings. If we block ICMP completely, diagnostics, reliability, and network performance may suffer as a result because important mechanisms are disabled when the ICMP protocol is restricted.</p>
<p>Some of the reasons why ICMP shouldn't be blocked:</p>
<ul>
<li>Path MTU discovery (PMTUD) is used to determine the maximum transmission unit size on network devices that connects the source and destination to avoid IP fragmentation. TCP depends on ICMP packets of type 3 code 4 for "Path MTU Discovery". ICMP type 3, code 4, and max packet size are returned when a packet exceeds the MTU size of a network device on the connected path. When these ICMP messages are blocked, the destination system continuously requests undelivered packets and the source system continues to resend them infinitely but to no avail. The behaviour can result in an ICMP <a href="https://en.wikipedia.org/wiki/Black_hole_%28networking%29">black hole</a> (congested IP connections and broken transmissions).</li>
<li>Time to live (TTL) defines the lifespan of a data packet. A network with ICMP blocked will not receive type 11, time exceeded, code 0, time exceeded in transit error messages. This means that the source host will not be notified to increase the lifespan of the data to successfully reach the destination, if the datagram fails to reach the destination.</li>
<li>Poor performance because of blocking ICMP redirect. ICMP redirect is used by a router to inform a host of a direct path from the source host to a destination host. This reduces the amount of hops data has to travel through to reach the destination. With ICMP blocked, the host will not be aware of the most optimal route to the destination.</li>
</ul>
<p>In the above setup we allow ICMP, but put a "rate limit" on the number of ping requests the router will answer. With the <code>max-pkt-rate 100/10</code> modifier the router will stop responding to pings if we get a more than a 100 pings in 10 seconds.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Should you still want to completely block ICMP for some reason, simply remove the 3 rules after the "Allow ICMP" comment.</p>
<p>Now we get to the LAN segment for the grown-ups in the house.</p>
<pre>#---------------------------------#
# Grown-ups LAN Setup
#---------------------------------#
# Allow any computer or device on the grown-ups LAN to send data packets in
# through the NIC. This means any computer attached to this network interface
# can pass in data reaching anywhere, i.e. the Internet or any of the computers
# attached to the router.
pass in on $g_lan
# Always block DNS queries not addressed to our DNS server.
block return in quick on $g_lan proto { udp tcp } to ! $g_lan port { 53 853 }
# I have a network printer I don't want to "phone home", so I block that.
# The network printer has the IP address 192.168.1.8.
block in quick on $g_lan from 192.168.1.8
# Allow data packets to pass from the router out through the NIC to the
# computers or devices attached to it on the grown-ups NIC.
# Without this we can't even ping computers attached to the grown-ups NIC from
# the router itself.
pass out on $g_lan inet keep state</pre>
<p>In this example I have a network printer attached to the grown-ups network that I don't want to access the Internet or anywhere else (just in case it has some kind of spying firmware). I do that by saying, <i>block all data coming in on em1 from the IP address 192.168.1.8 going to any IP address</i>.</p>
<p>Also we make sure that all DNS requests on port 53 (regular DNS) and 853 (DNS over TLS) are always blocked if they are not addressed to our DNS server.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Previously I used to redirect all traffic on port 53 not addressed to our DNS server back to our DNS server. I did that because when we block the DNS request on port 53, whether with a <code>return</code> or <code>drop</code>, the request will timeout on the client, which will make most clients cause a delay in the reply. I have since changed it to a block because I believe that it is the more correct approach. All clients need to realize that communication on port 53 is blocked, unless it is addressed to our DNS server. This is also important when we're troubleshooting our network. If we get a redirected reply from our DNS server we might not notice that we have been redirected.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> DNS primarily uses the User Datagram Protocol (UDP) on port number 53 to serve requests, but when the length of the answer exceeds 512 bytes and both client and server support EDNS, larger UDP packets are used. Otherwise, the query is sent again using the Transmission Control Protocol (TCP). Some DNS resolver implementations use TCP for all queries. As such we need both the UDP and TCP protocols in rule for port 53.</p>
<p>The childrens part of the LAN is very similar.</p>
<pre>#---------------------------------#
# Childrens LAN Setup
#---------------------------------#
# Allow any computers or devices on the childrens LAN to send data packets in
# through the NIC. This means any computer attached to this network interface
# can pass in data reaching anywhere, i.e. the Internet or any of the computers
# attached to the router.
pass in on $c_lan
# Always block DNS queries not addressed to our DNS server.
block return in quick on $c_lan proto { udp tcp} to ! $c_lan port { 53 853 }
# Allow data packets to pass from the router out through the NIC to the
# computers or devices attached to it on the grown-ups NIC.
# Without this we can't even ping computers attached to the childrens NIC from
# the router itself.
pass out on $c_lan inet keep state</pre>
<p>Currently both the grown-ups and the children have the same access to the Internet. A more restricted setup is mentioned in the <a href="#passlist">childrens pass list</a> section.</p>
<p>Then we get to the DMZ, i.e. the NIC with a publicly facing web server. Since we have a publicly facing web server we set up a couple of restrictions. Should the web server ever get compromised the intruder will have a hard time figuring out what else is located on our internal network.</p>
<p>We block all access except for DHCP, in order for the web server to get an IP address from our router, and then <b>only manually</b> open other things up whenever we need to update the machine or do something else. I have commented out the options we need, when we need to open things up, leaving the restricting parts enabled. When you need to update the server you open up for DNS and general access to the Internet.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Rather than manually changing the ruleset each time we need to open up for the web server to be updated, we can also use an <a href="https://man.openbsd.org/pf.conf#ANCHORS">anchor</a>, but for simplicity's sake we don't do that here.</p>
<pre>#---------------------------------#
# DMZ Setup
#---------------------------------#
# Allow any computer or device attached to the DMZ NIC to make DNS queries
# (uncomment if you need it).
#pass in on $dmz inet proto udp from any port 53
# Always block DNS queries not addressed to our DNS server on the router.
block return in quick on $dmz proto { udp tcp} to ! $dmz port { 53 853 }
# When you want any computer attached to the DMZ NIC to access the Internet
# uncomment this to open up for access. This is relevant when you need to
# upgrade a computer.
#pass in on $dmz inet
# No matter what, we do not want the DMZ segment to reach any of the other
# network segments so we explicitly use a block last.
#
# We have several options. If we use this:
#
# block drop in on $dmz to 192.168/16
#
# Then we block for all subnets, but this also means that the computers
# attached to the DMZ NIC cannot do DNS queries when they need to be upgraded.
#
# In my opinion it is much better to be explicit and block the specific
# segments we want blocked.
#
# This blocks computers on the DMZ NIC from reaching any computers or devices
# on the other two networking segments.
block drop in on $dmz to { $g_lan:network $c_lan:network }
# Last, we need to pass out data packets coming from the DMZ NIC to computers
# attached to it, otherwise nobody can "talk" to them.
# Without this we cannot even ping computers attached to the DMZ NIC from the
# router itself.
pass out on $dmz inet keep state</pre>
<p>Now we come to the network address translation (NAT). This is where the router routes packages from one segment of the network to another, in this specific case from our internal network to the Internet outside, and then any reply coming from the Internet outside, back in to the originator of the transmission. I prefer the <code>:network</code> parameter, which translates to the network(s) attached to the NIC, and I prefer to be specific with one rule for each relevant segment.</p>
<pre>#---------------------------------#
# NAT
#---------------------------------#
pass out on $ext_if inet from $g_lan:network to any nat-to ($ext_if)
pass out on $ext_if inet from $c_lan:network to any nat-to ($ext_if)
pass out on $ext_if inet from $dmz:network to any nat-to ($ext_if)</pre>
<p>PF will keep a track of all traffic and when e.g. a web browser on the grown-ups LAN requests a web page on some website on the Internet, the response from the web server on the Internet gets routed through our external NIC through to our internal grown-ups LAN NIC and then straight to the computer that originated the request.</p>
<p>Last we get to the redirecting part of our ruleset. This is where we allow traffic from the Internet outside in to our publicly facing web server on the DMZ NIC. You should, of course, leave this part out if you don't have any publicly facing servers that requires redirection. In this example I'm only allowing IPv4 traffic.</p>
<pre>#---------------------------------#
# Redirects
#---------------------------------#
# Our web server is 192.168.3.2 - let the Internet have access to it.
pass in on $ext_if inet proto tcp to $ext_if port { 80 443 } rdr-to 192.168.3.2</pre>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> Redirects always goes last in the ruleset!</p>
<p>That's it for our basic setup of firewall rules.</p>
<h3 id="passlist">The childrens pass list</h3>
<p>If you want to block the entire Internet for the children with the exception of a few websites or perhaps a few game servers, you need to figure out what the IP addresses of those services are and create a pass list using those IP addresses.</p>
<p>If it is a single website with a single IP address it is very easy and you can do it with this rule placed last in the childrens section (you need to replace the x.x.x.x part with the relevant IP address):</p>
<pre>#---------------------------------#
# Childrens LAN Setup
#---------------------------------#
# Allow any computer or device attached to the childrens NIC to make DNS
# queries.
pass in on $c_lan inet proto udp from any port 53
# Always block DNS queries not addressed to our DNS server.
block return in quick on $c_lan proto { udp tcp} to ! $c_lan port { 53 853 }
# Then allow any computer or device attached on the childrens LAN to reach
# the IP address x.x.x.x only.
pass in on $c_lan to x.x.x.x</pre>
<p>If the website has multiple IP addresses you need to figure out what those are. Sometimes a domain name lookup can reveal all the relevant IP addresses at once, such as <code>$ dig example.com ANY</code> or <code>drill example.com ANY</code>. At other times you need to repeat the lookup multiple times at different intervals in the day in order to get the full range of IP addresses. You can also do that by setting up an automated script.</p>
<p>Sometimes you may need to contact the relevant company and ask if you can get the IP range for your pass list (some companies keep the information public, others refuse to release the information out of fear for malicious usage). Once you have determined what the IP range is you can put those into a PF <code>table</code> and then use that.</p>
<p>In this example we add a new table to the table section of the rules and then change the settings in the childrens rules.</p>
<pre>#---------------------------------#
# Tables
#---------------------------------#
...
# Whitelist for the children.
table <passlist> { x.x.x.x y.y.y.y z.z.z.z }</pre>
<p>And then in the childrens section change:</p>
<pre>pass in on $c_lan to x.x.x.x</pre>
<p>to:</p>
<pre>pass in on $c_lan to <passlist></pre>
<p>It is not always possible to get all the needed IP addresses into a pass list all at once, but by monitoring the network, using e.g. <a href="https://man.openbsd.org/tcpdump">tcpdump</a>, when the game is trying to access a server, you can put together a working list, bit by bit. I have managed to do it with Mojang login servers and Minecraft servers and multiple other games.</p>
<h4 id="persistent-table">Using a persistent table</h4>
<p>Another approach to IP collecting for a pass list is to use a <a href="https://man.openbsd.org/pf.conf#TABLES">persistent table</a> in combination with <code>/etc/rc.local</code> and domain name lookups. <code>/etc/rc.local</code> is only run <b>after</b> PF is started and as such problems with domain name resolving will not cause PF any problems.</p>
<p>Should you want to run with the persistent table solution you can do it by adding a persistent table to the table section in <code>/etc/pf.conf</code>:</p>
<pre>table <passlist> persist</pre>
<p>In the childrens section you still need to pass data in that goes to the pass list like in the above:</p>
<pre>pass in on $c_lan to <passlist></pre>
<p>Then in <code>/etc/rc.local</code> you can add the following command:</p>
<pre>pfctl -t passlist -T add example.com</pre>
<p>Where <code>example.com</code> is the domain you want PF to lookup.</p>
<p>Whenever your kids cannot get access because the valid IP addresses might have changed, you can login to the firewall and then manually update the table with more IP addresses by running the command manually:</p>
<pre><b># pfctl -t passlist -T add examples.com</b></pre>
<p>If you want to see what IP addresses have been added to the list you can do it with:</p>
<pre><b># pfctl -t passlist -T show</b>
74.6.143.25
74.6.143.26
74.6.231.20
74.6.231.21
98.137.11.163
98.137.11.164
216.58.208.110
2001:4998:24:120d::1:0
2001:4998:24:120d::1:1
2001:4998:44:3507::8000
2001:4998:44:3507::8001
2001:4998:124:1507::f000
2001:4998:124:1507::f001
2a00:1450:400e:80e::200e
</pre>
<p>Eventually you can add all the IP addresses you collect (before they get flushed) into a physical file as the <code>persist</code> option can take input from a file as well:</p>
<pre>table <passlist> persist file "/etc/pf-passlist.txt"</pre>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> The file will not get IP addresses added using the <code>add</code> option to <code>pfctl</code>. A persistent table either resides in memory or on a file, but the <code>add</code> option cannot write to disk, only to memory. A persistent table from a file is one you need to manually edit with a text editor.</p>
<h3 id="loading-ruleset">Loading the rules</h3>
<p>Once you have finished setting up your ruleset you can test your rules with:</p>
<pre><b># pfctl -nf /etc/pf.conf</b></pre>
<p>If all is well, you load the ruleset by removing the <code>-n</code> option:</p>
<pre><b># pfctl -f /etc/pf.conf</b></pre>
<p>Take a look at the translated result with:</p>
<pre><b># pfctl -s rules</b></pre>
<h3 id="pf-dhcp">Don't try to block DHCP</h3>
<p>Just as a note, you cannot block access to dhcpd (port 67) via PF because on OpenBSD both <a href="https://man.openbsd.org/dhcpd">dhcpd</a> and dhclient use <a href="https://man.openbsd.org/bpf">bpf</a> by default for receiving and sending packets. This means that the packets are send and received before any filtering is done by PF.</p>
<p>Since bpf provides a raw interface to data link layers in a protocol-independent fashion, all packets on the network, even those destined for other hosts, are accessible through bpf.</p>
<p>See the post <a href="https://misc.openbsd.narkive.com/7SGmbxm0/allow-dhcpd-with-pf">allow dhcpd with pf</a> on the OpenBSD mailing list for relevant comments about this behavior.</p>
<h3 id="logging">Logging and monitoring</h3>
<p>This is an example output from the PF log of blocked attempts to access the external NIC on a setup of mine. I have cleaned out the output a bit and removed some specific data, and 0.0.0.0 is of course not my public IP address, but you already knew that right ;)</p>
<pre><code class="command"># tcpdump -n -e -ttt -r /var/log/pflog</code>
23:11:12 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3422: S 1501043655:1501043655(0) win 1024
23:11:12 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3481: S 311078394:311078394(0) win 1024
23:11:31 rule 14/(match) block in on em0: 176.214.44.229.25197 > 0.0.0.0.23: S 2084440900:2084440900(0) win 33620
23:11:33 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3431: S 2774981044:2774981044(0) win 1024
23:11:43 rule 14/(match) block in on em0: 81.68.114.52.17191 > 0.0.0.0.23: S 1346864438:1346864438(0) win 26375
23:12:08 rule 14/(match) block in on em0: 193.27.229.26.53865 > 0.0.0.0.443: S 1057596009:1057596009(0) win 1024
23:12:31 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.4186: S 1233742605:1233742605(0) win 1024
23:12:44 rule 14/(match) block in on em0: 74.120.14.70.65509 > 0.0.0.0.9125: S 1836577847:1836577847(0) win 1024 <mss 1460> [tos 0x20]
23:12:44 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.4128: S 2112968453:2112968453(0) win 1024
23:13:15 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3669: S 3627248539:3627248539(0) win 1024
23:13:19 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3654: S 3889665614:3889665614(0) win 1024
23:13:29 rule 14/(match) block in on em0: 45.129.33.129.42239 > 0.0.0.0.4997: S 2249816896:2249816896(0) win 1024
23:13:37 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3612: S 3797528151:3797528151(0) win 1024
23:14:03 rule 14/(match) block in on em0: 190.207.89.17.64372 > 0.0.0.0.445: S 1097568353:1097568353(0) win 8192 <mss 1460,nop,wscale 2,nop,nop,sackOK> (DF)
23:14:15 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.4219: S 2834775769:2834775769(0) win 1024
23:14:39 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.3702: S 1855726637:1855726637(0) win 1024
23:14:39 rule 14/(match) block in on em0: 45.129.33.4.45980 > 0.0.0.0.4210: S 3052103070:3052103070(0) win 1024</pre>
<p>As you can see it's quite busy, and I have nothing running that is facing the Internet on that setup.</p>
<p>You can also monitor PF in real time with:</p>
<pre># <b>tcpdump -n -e -ttt -i pflog0</b></pre>
<h2 id="domain-name-service">DNS</h2>
<p><a href="https://en.wikipedia.org/wiki/Domain_Name_System#Operation">Domain Name Service (DNS)</a> is used to translate a domain name into an IP address or vise versa. For example, when you type <a href="https://wikipedia.org">wikipedia.org</a> in your web browsers address field, an authoritative DNS server translates the domain name "wikipedia.org" to an IPv4 address such as 91.198.174.192 and/or IPv6 address such as 2620:0:862:ed1a::1.</p>
<p>DNS is also used, among many other things, to store information about which mail servers a specific domain name belongs to, if any.</p>
<p>If you're running a UNIX-like operating system, you can start up a terminal and try to perform a manual domain name lookup with <code>host</code>:</p>
<pre><b>$ host wikipedia.org</b>
wikipedia.org has address 91.198.174.192
wikipedia.org has IPv6 address 2620:0:862:ed1a::1
wikipedia.org mail is handled by 10 mx1001.wikimedia.org.
wikipedia.org mail is handled by 50 mx2001.wikimedia.org.</pre>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> If you don't have <a href="https://man.openbsd.org/host">host</a> installed, depending on what platform you're on, you might need to install <a href="https://www.isc.org/bind/">bind</a> or <code>dnsutils</code>. You can also use something like <a href="https://man.openbsd.org/dig">dig</a>, also from <a href="https://www.isc.org/bind/">bind</a>, or <a href="https://linux.die.net/man/1/drill">drill</a> from <a href="https://nlnetlabs.nl/projects/ldns/about/">ldns</a></p>
<p>The following list describes some of the terms associated with DNS:</p>
<ul>
<li><code>Forward DNS</code>
<ul>
<li><p>Mapping of hostnames and domain names to IP addresses.</p></li>
</ul>
</li>
<li><code>Reverse DNS</code>
<ul>
<li><p>Mapping of IP addresses to hostnames and domain names.</p></li>
</ul>
</li>
<li><code>Resolver</code>
<ul>
<li><p>A system through which a machine queries a name server for zone information, i.e. another name for a "DNS server".</p></li>
</ul>
</li>
<li><code>Root zone</code>
<ul>
<li><p>The beginning of the Internet zone hierarchy. All zones fall under the <a href="https://en.wikipedia.org/wiki/DNS_root_zone">root zone</a>, similar to how all files in a file system fall under the root directory.</p></li>
</ul>
</li>
</ul>
<p>This is an example of zones:</p>
<ul>
<li><code>.</code> (a period) is how the root zone is usually referred to in documentation.</li>
<li><code>org.</code> is a <a href="https://en.wikipedia.org/wiki/Top-level_domain">Top-Level Domain (TLD)</a> under the root zone.</li>
<li><code>wikipedia.org.</code> is a zone under the <code>org.</code> TLD.</li>
<li><code>1.168.192.in-addr.arpa</code> is a zone referencing all IP addresses which fall under the <code>192.168.1.*</code> IP address space.</li>
</ul>
<p>When a computer on the Internet needs to resolve a domain name the resolver breaks the name up into its labels from right to left. The first component, the Top-Level Domain (TLD), is queried using a root server to obtain the responsible authoritative server. Queries for each label return more specific name servers until a name server returns the answer of the original query.</p>
<p>Even though any local DNS server can implement its own private root name servers, the term "root name server" is used to describe <a href="https://en.wikipedia.org/wiki/Root_name_server#Root_server_addresses">the thirteen well-known root name servers</a> that implement the root name space domain for the Internet's official global implementation of the Domain Name System. Resolvers use a small 3 KB <code>root.hints</code> file, published by <a href="https://en.wikipedia.org/wiki/InterNIC">Internic</a>, to bootstrap this initial list of root server addresses. For many pieces of software, including Unbound, this list is built into the software.</p>
<p>On the <a href="https://www.iana.org/domains/root/db">The Root Zone Database</a> you can lookup the delegation details of top-level domains, including TLDs such as .com, .org, and country-code TLDs such as .uk and .de.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Since you can lookup delegation details of top-level domains, you might expect that it would be possible to go deeper and actually look up every domain that a particular domain server has registered in its database. Since we, for example, can get a list of the responsible top-level domain servers for the <a href="https://www.iana.org/domains/root/db/dk.html">.dk</a> TLD, we might expect that it is possible to query one of those listed name servers for its entire database of authoritative servers, and then query one of those for all registered domains in its database. But that's not how DNS works. There are only two ways that a DNS servers complete database map can be obtained. Either you have to have access to the relevant zone files, or you need to physically construct a database by examining DNS traffic through a recursive DNS server and then reconstruct zone data based upon the data that is collected, until you get everything, which is highly unlikely that you ever will.</p>
<p>There are two DNS server configuration types:</p>
<ul>
<li><code>Authoritative</code>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Authoritative_name_server">Authoritative name servers</a> publish IP addresses for domains under their authoritative control. These servers are listed as being at the top of the authority chain for their respective domains, and are capable of providing a definitive answer.</p>
<p>Authoritative name servers can be primary name servers, also known as master servers, i.e. they contain the original set of data, or they can be secondary or slave name servers, containing data copies usually obtained from synchronization directly with the primary server.</p>
<p>An authoritative name server is a name server that only gives answers to DNS queries from data that has been configured by an original source, for example, the domain administrator.</p>
<p>Every DNS zone must be assigned a set of authoritative name servers. This set of servers is stored in the parent domain zone with name server (NS) records. An authoritative server indicates its status of supplying definitive answers, deemed authoritative, by setting a protocol flag, called the "Authoritative Answer" (AA) bit in its responses.</p>
<p>You can use a network tool such as <a href="https://man.openbsd.org/dig">dig</a> or <a href="https://linux.die.net/man/1/drill">drill</a> to lookup a domain name, the tool will reply with an authoritative flag that reveals whether the DNS server you have queried is the authoritative one.</p>
</li>
</ul>
</li>
<li><code>Recursive</code>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Domain_Name_System#Recursive_and_caching_name_server">Recursive servers</a>, sometimes called "DNS caches" or "caching-only name servers", provide DNS name resolution for applications, by relaying the requests of the client application to the chain of authoritative name servers to fully resolve a network name. They also (typically) cache the result to answer potential future queries within a certain expiration time period.</p>
<p>Most Internet users access a public recursive DNS server provided by their ISP or a public DNS service provider.</p>
<p>In theory, authoritative name servers are sufficient for the operation of the Internet. However, with only authoritative name servers operating, every DNS query must start with recursive queries at the root zone of the Domain Name System and each user system would have to implement resolver software capable of recursive operation. To improve efficiency, reduce DNS traffic across the Internet, and increase performance in end-user applications, the Domain Name System supports recursive resolvers.</p>
<p>A recursive DNS query is one for which the DNS server answers the query completely by querying other name servers as needed.</p>
</li>
</ul>
</li>
</ul>
<p>A nameserver can be both authoritative and recursive at the same time, but it is recommended not to combine the configuration types. To be able to perform their work, authoritative servers should be available to all clients all the time. On the other hand, since the recursive lookup takes far more time than authoritative responses, recursive servers should be available to a restricted number of clients only, otherwise they are prone to <a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">distributed denial of service (DDoS) attacks</a>.</p>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> If needed, I recommend that you read "How DNS Works" in <a href="https://tldp.org/LDP/nag2/x-087-2-resolv.howdnsworks.html">chapter 6 of the Linux Network Administrators Guide</a>. I also recommend that you read <a href="https://en.wikipedia.org/wiki/Domain_Name_System#Operation">Domain Name Service (DNS)</a> on Wikipedia.</p>
<h2 id="unbound">I present to you, Unbound</h2>
<p><a href="https://nlnetlabs.nl/projects/unbound/about/">Unbound</a> is a recursive, caching and validating Open Source DNS resolver with the following features:</p>
<ul>
<li>Cache with optional prefetching of popular items before they expire.</li>
<li>DNS over TLS (DoT) forwarding and server, with domain-validation.</li>
<li>DNS over HTTPS (DoH).</li>
<li>Query Name Minimization.</li>
<li>Aggressive Use of DNSSEC-Validated Cache.</li>
<li>Authority zones, for a local copy of the root zone.</li>
<li>DNS64.</li>
<li>DNSCrypt.</li>
<li>DNSSEC validating.</li>
<li>EDNS Client Subnet.</li>
</ul>
<p>Unbound is designed to be fast and secure and it incorporates modern features based on open standards. Late 2019, Unbound was <a href="https://ostif.org/wp-content/uploads/2019/12/X41-Unbound-Security-Audit-2019-Final-Report.pdf">rigorously audited</a>.</p>
<p class="info info-green" style="font-size:initial;"><b>TIP:</b> One of the main reasons to use Unbound over several other simple caching-only resolvers, such as <a href="https://en.wikipedia.org/wiki/Dnsmasq">dnsmasq</a> for example, is that if you do not use the <code>forward</code> option in Unbounds configuration, Unbound <b>will query the root servers directly</b> using their registered IP addresses listed in the <a href="https://www.iana.org/domains/root/files">Root Hints File</a>. This will free you of your ISP DNS servers and any public DNS servers, such as Google or Cloudflare, and whatever data recording, selling and manipulation they're doing is avoided. A simple caching server such as dnsmasq will always forward queries to another server, whereas Unbound queries the root servers directly and works its way down the domain chain until it gets the relevant record from the registered authoritative DNS server for the relevant domain. This means that the DNS server that specifically knows what you're looking for is also the one that is authoritative to answer the question.</p>
<p class="info info-red" style="font-size:initial;"><b>WARNING:</b> If your ISP is hijacking DNS traffic, Unbound will not help you in any way. See the section <a href="#dns-hijacking">DNS hijacking</a> for information on how you can determine if you DNS traffic is getting hijacked.</p>
<p>In our setup with Unbound, a query for a domain such as "wikipedia.org" will look like this:</p>
<ol>
<li>Your browser sends a query to the operating system with the question, "What is the IP address of wikipedia.org"?</li>
<li>The operating system, more specifically the resolver routines in the C library, which provide access to the Internet Domain Name System, will then forward the DNS request to the domain name server(s) listed in <a href="https://man.openbsd.org/resolv.conf">/etc/resolv.conf</a> (on UNIX-like operating systems).</li>
<li>Unbound receives the query and first looks for "wikipedia.org" in its cache and if not found, Unbound queries one of the root servers listed in its Root Hints File for the top-level domain ".org".</li>
<li>The root server replies with a referral to the relevant servers for the ".org" top-level domain.</li>
<li>Unbound then sends a query to one of the relevant servers asking for the authoritative DNS servers for "wikipedia.org".</li>
<li>The server replies with a referral to the authoritative name servers registered for "wikipedia.org".</li>
<li>Unbound then sends a query to one of those authoritative name servers and asks for the IP address for "wikipedia.org".</li>
<li>The authoritative name server replies by sending the IP address it has listed in its "A" (IPv4) and/or "AAAA" (IPv6) record for the domain "wikipedia.org".</li>
<li>Unbound receives the IP address from the authoritative name server and returns the answer to the client.</li>
<li>If enabled, Unbound then caches the information for a pre-determined length of time for future queries for the same domain.</li>
</ol>
<p>You can try to do a DNS <code>trace</code> yourself to see the above. I'm using <a href="https://linux.die.net/man/1/drill">drill</a> in this example with the <code>trace</code> option enabled.</p>
<pre><b># drill -T wikipedia.org</b>
. 518400 IN NS l.root-servers.net.
. 518400 IN NS k.root-servers.net.
. 518400 IN NS e.root-servers.net.
. 518400 IN NS a.root-servers.net.
. 518400 IN NS m.root-servers.net.
. 518400 IN NS h.root-servers.net.
. 518400 IN NS i.root-servers.net.
. 518400 IN NS f.root-servers.net.
. 518400 IN NS c.root-servers.net.
. 518400 IN NS b.root-servers.net.
. 518400 IN NS g.root-servers.net.
. 518400 IN NS d.root-servers.net.
. 518400 IN NS j.root-servers.net.
org. 172800 IN NS a0.org.afilias-nst.info.
org. 172800 IN NS a2.org.afilias-nst.info.
org. 172800 IN NS b0.org.afilias-nst.org.
org. 172800 IN NS b2.org.afilias-nst.org.
org. 172800 IN NS c0.org.afilias-nst.info.
org. 172800 IN NS d0.org.afilias-nst.org.
wikipedia.org. 86400 IN NS ns0.wikimedia.org.
wikipedia.org. 86400 IN NS ns1.wikimedia.org.
wikipedia.org. 86400 IN NS ns2.wikimedia.org.
wikipedia.org. 600 IN A 91.198.174.192</pre>
<p class="info info-blue" style="font-size:initial;"><b>NOTE:</b> Unbound has the ability to validate the responses it receives as correct. This is usually accomplished using <a href="https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions">Domain Name System Security Extensions (DNSSEC)</a> or by using 0x20-encoded random bits in the query to foil spoof attempts. With the exception of <a href="https://man.openbsd.org/unbound.conf#use~3">0x20-encoded random bits</a>, all the other validation settings such as <a href="https://man.openbsd.org/unbound.conf#harden~3">harden-glue</a> and <a href="https://man.openbsd.org/unbound.conf#harden~4">hardened dnssec-stripped data</a> are all enabled by default in Unbound on OpenBSD.</p>
<h2 id="blocking-with-dns">Blocking with DNS</h2>
<p>DNS blocking, also called filtering, or DNS spoofing, is the process in which you supply the client that does the query with a "fake" reply. We block a request for a valid IP address either by replying with a <a href="https://tools.ietf.org/html/rfc8020">NXDOMAIN</a>, meaning non-existent domain, or with a redirect to another IP address than the intended by the owner of the domain.</p>
<p>This enables us to create a list, or multiple lists, of domains we want to block and rather than providing the user with the correct IP address for a certain domain, we return the message that the domain is "non-existent", which will block the application for further communication to the intended destination.</p>