aboutsummaryrefslogtreecommitdiff
path: root/sbin/pfctl
diff options
context:
space:
mode:
Diffstat (limited to 'sbin/pfctl')
-rw-r--r--sbin/pfctl/Makefile34
-rw-r--r--sbin/pfctl/Makefile.depend21
-rw-r--r--sbin/pfctl/parse.y7879
-rw-r--r--sbin/pfctl/pf.os708
-rw-r--r--sbin/pfctl/pf_print_state.c461
-rw-r--r--sbin/pfctl/pf_ruleset.c539
-rw-r--r--sbin/pfctl/pfctl.8830
-rw-r--r--sbin/pfctl/pfctl.c3759
-rw-r--r--sbin/pfctl/pfctl.h202
-rw-r--r--sbin/pfctl/pfctl_altq.c1437
-rw-r--r--sbin/pfctl/pfctl_ioctl.h0
-rw-r--r--sbin/pfctl/pfctl_optimize.c1684
-rw-r--r--sbin/pfctl/pfctl_osfp.c1098
-rw-r--r--sbin/pfctl/pfctl_parser.c2101
-rw-r--r--sbin/pfctl/pfctl_parser.h392
-rw-r--r--sbin/pfctl/pfctl_qstats.c513
-rw-r--r--sbin/pfctl/pfctl_radix.c481
-rw-r--r--sbin/pfctl/pfctl_table.c716
-rw-r--r--sbin/pfctl/tests/Makefile12
-rw-r--r--sbin/pfctl/tests/Makefile.depend15
-rw-r--r--sbin/pfctl/tests/files/Makefile9
-rw-r--r--sbin/pfctl/tests/files/Makefile.depend10
-rw-r--r--sbin/pfctl/tests/files/pf0001.in8
-rw-r--r--sbin/pfctl/tests/files/pf0001.ok8
-rw-r--r--sbin/pfctl/tests/files/pf0002.in34
-rw-r--r--sbin/pfctl/tests/files/pf0002.ok22
-rw-r--r--sbin/pfctl/tests/files/pf0003.in13
-rw-r--r--sbin/pfctl/tests/files/pf0003.ok13
-rw-r--r--sbin/pfctl/tests/files/pf0004.in16
-rw-r--r--sbin/pfctl/tests/files/pf0004.ok62
-rw-r--r--sbin/pfctl/tests/files/pf0005.in6
-rw-r--r--sbin/pfctl/tests/files/pf0005.ok11
-rw-r--r--sbin/pfctl/tests/files/pf0006.in3
-rw-r--r--sbin/pfctl/tests/files/pf0006.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0007.in34
-rw-r--r--sbin/pfctl/tests/files/pf0007.ok27
-rw-r--r--sbin/pfctl/tests/files/pf0008.in2
-rw-r--r--sbin/pfctl/tests/files/pf0008.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0009.in3
-rw-r--r--sbin/pfctl/tests/files/pf0009.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0010.in31
-rw-r--r--sbin/pfctl/tests/files/pf0010.ok30
-rw-r--r--sbin/pfctl/tests/files/pf0011.in18
-rw-r--r--sbin/pfctl/tests/files/pf0011.ok18
-rw-r--r--sbin/pfctl/tests/files/pf0012.in5
-rw-r--r--sbin/pfctl/tests/files/pf0012.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0013.in22
-rw-r--r--sbin/pfctl/tests/files/pf0013.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0014.in6
-rw-r--r--sbin/pfctl/tests/files/pf0014.ok6
-rw-r--r--sbin/pfctl/tests/files/pf0016.in5
-rw-r--r--sbin/pfctl/tests/files/pf0016.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0018.in19
-rw-r--r--sbin/pfctl/tests/files/pf0018.ok21
-rw-r--r--sbin/pfctl/tests/files/pf0019.in9
-rw-r--r--sbin/pfctl/tests/files/pf0019.ok13
-rw-r--r--sbin/pfctl/tests/files/pf0020.in9
-rw-r--r--sbin/pfctl/tests/files/pf0020.ok16
-rw-r--r--sbin/pfctl/tests/files/pf0022.in8
-rw-r--r--sbin/pfctl/tests/files/pf0022.ok10
-rw-r--r--sbin/pfctl/tests/files/pf0023.in2
-rw-r--r--sbin/pfctl/tests/files/pf0023.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0024.in8
-rw-r--r--sbin/pfctl/tests/files/pf0024.ok7
-rw-r--r--sbin/pfctl/tests/files/pf0025.in4
-rw-r--r--sbin/pfctl/tests/files/pf0025.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0026.in2
-rw-r--r--sbin/pfctl/tests/files/pf0026.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0028.in7
-rw-r--r--sbin/pfctl/tests/files/pf0028.ok6
-rw-r--r--sbin/pfctl/tests/files/pf0030.in7
-rw-r--r--sbin/pfctl/tests/files/pf0030.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0031.in21
-rw-r--r--sbin/pfctl/tests/files/pf0031.ok19
-rw-r--r--sbin/pfctl/tests/files/pf0032.in7
-rw-r--r--sbin/pfctl/tests/files/pf0032.ok6
-rw-r--r--sbin/pfctl/tests/files/pf0034.in5
-rw-r--r--sbin/pfctl/tests/files/pf0034.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0035.in5
-rw-r--r--sbin/pfctl/tests/files/pf0035.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0038.in5
-rw-r--r--sbin/pfctl/tests/files/pf0038.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0039.in25
-rw-r--r--sbin/pfctl/tests/files/pf0039.ok24
-rw-r--r--sbin/pfctl/tests/files/pf0040.in20
-rw-r--r--sbin/pfctl/tests/files/pf0040.ok20
-rw-r--r--sbin/pfctl/tests/files/pf0041.in12
-rw-r--r--sbin/pfctl/tests/files/pf0041.ok12
-rw-r--r--sbin/pfctl/tests/files/pf0047.in67
-rw-r--r--sbin/pfctl/tests/files/pf0047.ok61
-rw-r--r--sbin/pfctl/tests/files/pf0048.in13
-rw-r--r--sbin/pfctl/tests/files/pf0048.ok17
-rw-r--r--sbin/pfctl/tests/files/pf0049.in7
-rw-r--r--sbin/pfctl/tests/files/pf0049.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0050.in4
-rw-r--r--sbin/pfctl/tests/files/pf0050.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0052.in7
-rw-r--r--sbin/pfctl/tests/files/pf0052.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0053.in4
-rw-r--r--sbin/pfctl/tests/files/pf0053.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0055.in18
-rw-r--r--sbin/pfctl/tests/files/pf0055.ok28
-rw-r--r--sbin/pfctl/tests/files/pf0056.in2
-rw-r--r--sbin/pfctl/tests/files/pf0056.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0057.in4
-rw-r--r--sbin/pfctl/tests/files/pf0057.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0060.in11
-rw-r--r--sbin/pfctl/tests/files/pf0060.ok7
-rw-r--r--sbin/pfctl/tests/files/pf0061.in4
-rw-r--r--sbin/pfctl/tests/files/pf0061.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0065.in2
-rw-r--r--sbin/pfctl/tests/files/pf0065.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0067.in3
-rw-r--r--sbin/pfctl/tests/files/pf0067.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0069.in2
-rw-r--r--sbin/pfctl/tests/files/pf0069.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0070.in2
-rw-r--r--sbin/pfctl/tests/files/pf0070.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0071.in2
-rw-r--r--sbin/pfctl/tests/files/pf0071.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0072.in3
-rw-r--r--sbin/pfctl/tests/files/pf0072.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0074.in1
-rw-r--r--sbin/pfctl/tests/files/pf0074.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0075.in3
-rw-r--r--sbin/pfctl/tests/files/pf0075.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0077.in5
-rw-r--r--sbin/pfctl/tests/files/pf0077.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0078.in2
-rw-r--r--sbin/pfctl/tests/files/pf0078.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0079.in2
-rw-r--r--sbin/pfctl/tests/files/pf0079.ok1
-rw-r--r--sbin/pfctl/tests/files/pf0081.in12
-rw-r--r--sbin/pfctl/tests/files/pf0081.ok32
-rw-r--r--sbin/pfctl/tests/files/pf0082.in15
-rw-r--r--sbin/pfctl/tests/files/pf0082.ok13
-rw-r--r--sbin/pfctl/tests/files/pf0084.in17
-rw-r--r--sbin/pfctl/tests/files/pf0084.ok8
-rw-r--r--sbin/pfctl/tests/files/pf0085.in3
-rw-r--r--sbin/pfctl/tests/files/pf0085.ok6
-rw-r--r--sbin/pfctl/tests/files/pf0087.in24
-rw-r--r--sbin/pfctl/tests/files/pf0087.ok22
-rw-r--r--sbin/pfctl/tests/files/pf0088.in32
-rw-r--r--sbin/pfctl/tests/files/pf0088.ok22
-rw-r--r--sbin/pfctl/tests/files/pf0089.in25
-rw-r--r--sbin/pfctl/tests/files/pf0089.ok11
-rw-r--r--sbin/pfctl/tests/files/pf0090.in5
-rw-r--r--sbin/pfctl/tests/files/pf0090.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0091.in11
-rw-r--r--sbin/pfctl/tests/files/pf0091.ok10
-rw-r--r--sbin/pfctl/tests/files/pf0092.in30
-rw-r--r--sbin/pfctl/tests/files/pf0092.ok26
-rw-r--r--sbin/pfctl/tests/files/pf0094.in4
-rw-r--r--sbin/pfctl/tests/files/pf0094.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0095.in4
-rw-r--r--sbin/pfctl/tests/files/pf0095.include2
-rw-r--r--sbin/pfctl/tests/files/pf0095.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0096.in5
-rw-r--r--sbin/pfctl/tests/files/pf0096.ok5
-rw-r--r--sbin/pfctl/tests/files/pf0097.in4
-rw-r--r--sbin/pfctl/tests/files/pf0097.ok3
-rw-r--r--sbin/pfctl/tests/files/pf0098.in3
-rw-r--r--sbin/pfctl/tests/files/pf0098.ok2
-rw-r--r--sbin/pfctl/tests/files/pf0100.in20
-rw-r--r--sbin/pfctl/tests/files/pf0100.ok18
-rw-r--r--sbin/pfctl/tests/files/pf0101.in8
-rw-r--r--sbin/pfctl/tests/files/pf0101.ok4
-rw-r--r--sbin/pfctl/tests/files/pf0102.in9
-rw-r--r--sbin/pfctl/tests/files/pf0102.ok8
-rw-r--r--sbin/pfctl/tests/files/pf0104.in10
-rw-r--r--sbin/pfctl/tests/files/pf0104.ok7
-rw-r--r--sbin/pfctl/tests/files/pf1001.in2
-rw-r--r--sbin/pfctl/tests/files/pf1001.ok2
-rw-r--r--sbin/pfctl/tests/files/pf1002.in6
-rw-r--r--sbin/pfctl/tests/files/pf1002.ok6
-rw-r--r--sbin/pfctl/tests/files/pf1003.in3
-rw-r--r--sbin/pfctl/tests/files/pf1003.ok3
-rw-r--r--sbin/pfctl/tests/files/pf1004.in6
-rw-r--r--sbin/pfctl/tests/files/pf1004.ok6
-rw-r--r--sbin/pfctl/tests/files/pf1005.in3
-rw-r--r--sbin/pfctl/tests/files/pf1005.ok3
-rw-r--r--sbin/pfctl/tests/files/pf1006.in2
-rw-r--r--sbin/pfctl/tests/files/pf1006.ok2
-rw-r--r--sbin/pfctl/tests/files/pf1007.in1
-rw-r--r--sbin/pfctl/tests/files/pf1007.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1008.in1
-rw-r--r--sbin/pfctl/tests/files/pf1008.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1009.in1
-rw-r--r--sbin/pfctl/tests/files/pf1009.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1010.in2
-rw-r--r--sbin/pfctl/tests/files/pf1010.ok2
-rw-r--r--sbin/pfctl/tests/files/pf1011.in1
-rw-r--r--sbin/pfctl/tests/files/pf1011.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1012.in1
-rw-r--r--sbin/pfctl/tests/files/pf1012.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1013.in1
-rw-r--r--sbin/pfctl/tests/files/pf1013.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1014.in1
-rw-r--r--sbin/pfctl/tests/files/pf1014.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1015.in1
-rw-r--r--sbin/pfctl/tests/files/pf1015.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1016.in1
-rw-r--r--sbin/pfctl/tests/files/pf1016.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1017.in1
-rw-r--r--sbin/pfctl/tests/files/pf1017.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1018.in1
-rw-r--r--sbin/pfctl/tests/files/pf1018.ok2
-rw-r--r--sbin/pfctl/tests/files/pf1019.in1
-rw-r--r--sbin/pfctl/tests/files/pf1019.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1020.in3
-rw-r--r--sbin/pfctl/tests/files/pf1020.include4
-rw-r--r--sbin/pfctl/tests/files/pf1020.ok2
-rw-r--r--sbin/pfctl/tests/files/pf1021.in1
-rw-r--r--sbin/pfctl/tests/files/pf1021.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1022.in1
-rw-r--r--sbin/pfctl/tests/files/pf1022.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1023.in3
-rw-r--r--sbin/pfctl/tests/files/pf1023.ok3
-rw-r--r--sbin/pfctl/tests/files/pf1024.in1
-rw-r--r--sbin/pfctl/tests/files/pf1024.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1025.in1
-rw-r--r--sbin/pfctl/tests/files/pf1025.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1026.in1
-rw-r--r--sbin/pfctl/tests/files/pf1026.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1027.in1
-rw-r--r--sbin/pfctl/tests/files/pf1027.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1028.in1
-rw-r--r--sbin/pfctl/tests/files/pf1028.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1029.in1
-rw-r--r--sbin/pfctl/tests/files/pf1029.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1030.in1
-rw-r--r--sbin/pfctl/tests/files/pf1030.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1031.in1
-rw-r--r--sbin/pfctl/tests/files/pf1031.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1032.in1
-rw-r--r--sbin/pfctl/tests/files/pf1032.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1033.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1033.in1
-rw-r--r--sbin/pfctl/tests/files/pf1034.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1034.in1
-rw-r--r--sbin/pfctl/tests/files/pf1035.in1
-rw-r--r--sbin/pfctl/tests/files/pf1035.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1036.in1
-rw-r--r--sbin/pfctl/tests/files/pf1036.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1037.in1
-rw-r--r--sbin/pfctl/tests/files/pf1037.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1038.in1
-rw-r--r--sbin/pfctl/tests/files/pf1038.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1039.in1
-rw-r--r--sbin/pfctl/tests/files/pf1039.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1040.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1040.in1
-rw-r--r--sbin/pfctl/tests/files/pf1040.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1041.in1
-rw-r--r--sbin/pfctl/tests/files/pf1041.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1042.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1042.in1
-rw-r--r--sbin/pfctl/tests/files/pf1043.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1043.in1
-rw-r--r--sbin/pfctl/tests/files/pf1044.in1
-rw-r--r--sbin/pfctl/tests/files/pf1044.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1045.in1
-rw-r--r--sbin/pfctl/tests/files/pf1045.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1046.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1046.in1
-rw-r--r--sbin/pfctl/tests/files/pf1047.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1047.in1
-rw-r--r--sbin/pfctl/tests/files/pf1048.in1
-rw-r--r--sbin/pfctl/tests/files/pf1048.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1049.in1
-rw-r--r--sbin/pfctl/tests/files/pf1049.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1050.in1
-rw-r--r--sbin/pfctl/tests/files/pf1050.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1051.in1
-rw-r--r--sbin/pfctl/tests/files/pf1051.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1052.in1
-rw-r--r--sbin/pfctl/tests/files/pf1052.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1053.in1
-rw-r--r--sbin/pfctl/tests/files/pf1053.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1054.in3
-rw-r--r--sbin/pfctl/tests/files/pf1054.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1055.in1
-rw-r--r--sbin/pfctl/tests/files/pf1055.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1056.in1
-rw-r--r--sbin/pfctl/tests/files/pf1056.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1057.in1
-rw-r--r--sbin/pfctl/tests/files/pf1057.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1058.in1
-rw-r--r--sbin/pfctl/tests/files/pf1058.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1059.in1
-rw-r--r--sbin/pfctl/tests/files/pf1059.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1060.in1
-rw-r--r--sbin/pfctl/tests/files/pf1060.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1061.in1
-rw-r--r--sbin/pfctl/tests/files/pf1061.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1062.in1
-rw-r--r--sbin/pfctl/tests/files/pf1062.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1063.in1
-rw-r--r--sbin/pfctl/tests/files/pf1063.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1064.in1
-rw-r--r--sbin/pfctl/tests/files/pf1064.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1065.in1
-rw-r--r--sbin/pfctl/tests/files/pf1065.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1066.in1
-rw-r--r--sbin/pfctl/tests/files/pf1066.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1067.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1067.in1
-rw-r--r--sbin/pfctl/tests/files/pf1068.in1
-rw-r--r--sbin/pfctl/tests/files/pf1068.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1069.in1
-rw-r--r--sbin/pfctl/tests/files/pf1069.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1070.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1070.in2
-rw-r--r--sbin/pfctl/tests/files/pf1070.include2
-rw-r--r--sbin/pfctl/tests/files/pf1071.in1
-rw-r--r--sbin/pfctl/tests/files/pf1071.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1072.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1072.in1
-rw-r--r--sbin/pfctl/tests/files/pf1073.in1
-rw-r--r--sbin/pfctl/tests/files/pf1073.ok1
-rw-r--r--sbin/pfctl/tests/files/pf1074.fail1
-rw-r--r--sbin/pfctl/tests/files/pf1074.in1
-rw-r--r--sbin/pfctl/tests/files/pf1075.in1
-rw-r--r--sbin/pfctl/tests/files/pf1075.ok1
-rwxr-xr-xsbin/pfctl/tests/macro.sh28
-rw-r--r--sbin/pfctl/tests/pfctl_test.c341
-rw-r--r--sbin/pfctl/tests/pfctl_test_list.inc186
327 files changed, 25208 insertions, 0 deletions
diff --git a/sbin/pfctl/Makefile b/sbin/pfctl/Makefile
new file mode 100644
index 000000000000..08ca9a7af81b
--- /dev/null
+++ b/sbin/pfctl/Makefile
@@ -0,0 +1,34 @@
+.include <src.opts.mk>
+
+PACKAGE=pf
+CONFS= pf.os
+PROG= pfctl
+MAN= pfctl.8
+
+SRCS = pfctl.c parse.y pfctl_parser.c pf_print_state.c pfctl_altq.c
+SRCS+= pfctl_osfp.c pfctl_radix.c pfctl_table.c pfctl_qstats.c
+SRCS+= pfctl_optimize.c
+SRCS+= pf_ruleset.c
+
+WARNS?= 2
+CFLAGS+= -Wall -Wmissing-prototypes -Wno-uninitialized
+CFLAGS+= -Wstrict-prototypes
+CFLAGS+= -DENABLE_ALTQ -I${.CURDIR}
+CFLAGS+= -I${SRCTOP}/lib/libpfctl -I${OBJTOP}/lib/libpfctl
+
+# Need to use "WITH_" prefix to not conflict with the l/y INET/INET6 keywords
+.if ${MK_INET6_SUPPORT} != "no"
+CFLAGS+= -DWITH_INET6
+.endif
+.if ${MK_INET_SUPPORT} != "no"
+CFLAGS+= -DWITH_INET
+.endif
+
+YFLAGS=
+
+LIBADD= m md pfctl
+
+HAS_TESTS=
+SUBDIR.${MK_TESTS}+= tests
+
+.include <bsd.prog.mk>
diff --git a/sbin/pfctl/Makefile.depend b/sbin/pfctl/Makefile.depend
new file mode 100644
index 000000000000..bc09f07d0227
--- /dev/null
+++ b/sbin/pfctl/Makefile.depend
@@ -0,0 +1,21 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ include \
+ include/arpa \
+ include/xlocale \
+ lib/${CSU_DIR} \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libmd \
+ lib/libnv \
+ lib/libpfctl \
+ lib/msun \
+ usr.bin/yacc.host \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
new file mode 100644
index 000000000000..127e2c257d69
--- /dev/null
+++ b/sbin/pfctl/parse.y
@@ -0,0 +1,7879 @@
+/* $OpenBSD: parse.y,v 1.554 2008/10/17 12:59:53 henning Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Markus Friedl. All rights reserved.
+ * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
+ * Copyright (c) 2001 Theo de Raadt. All rights reserved.
+ * Copyright (c) 2002,2003 Henning Brauer. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+%{
+#include <sys/cdefs.h>
+#define PFIOC_USE_LATEST
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#ifdef __FreeBSD__
+#include <sys/sysctl.h>
+#endif
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_codel.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+#include <net/altq/altq_fairq.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <err.h>
+#include <limits.h>
+#include <pwd.h>
+#include <grp.h>
+#include <md5.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+static struct pfctl *pf = NULL;
+static int debug = 0;
+static int rulestate = 0;
+static u_int16_t returnicmpdefault =
+ (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
+static u_int16_t returnicmp6default =
+ (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
+static int blockpolicy = PFRULE_DROP;
+static int failpolicy = PFRULE_DROP;
+static int require_order = 1;
+static int default_statelock;
+
+static TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
+static struct file {
+ TAILQ_ENTRY(file) entry;
+ FILE *stream;
+ char *name;
+ size_t ungetpos;
+ size_t ungetsize;
+ u_char *ungetbuf;
+ int eof_reached;
+ int lineno;
+ int errors;
+} *file, *topfile;
+struct file *pushfile(const char *, int);
+int popfile(void);
+int check_file_secrecy(int, const char *);
+int yyparse(void);
+int yylex(void);
+int yyerror(const char *, ...);
+int kw_cmp(const void *, const void *);
+int lookup(char *);
+int igetc(void);
+int lgetc(int);
+void lungetc(int);
+int findeol(void);
+
+static TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
+struct sym {
+ TAILQ_ENTRY(sym) entry;
+ int used;
+ int persist;
+ char *nam;
+ char *val;
+};
+int symset(const char *, const char *, int);
+char *symget(const char *);
+
+int atoul(char *, u_long *);
+
+enum {
+ PFCTL_STATE_NONE,
+ PFCTL_STATE_OPTION,
+ PFCTL_STATE_ETHER,
+ PFCTL_STATE_SCRUB,
+ PFCTL_STATE_QUEUE,
+ PFCTL_STATE_NAT,
+ PFCTL_STATE_FILTER
+};
+
+struct node_etherproto {
+ u_int16_t proto;
+ struct node_etherproto *next;
+ struct node_etherproto *tail;
+};
+
+struct node_proto {
+ u_int8_t proto;
+ struct node_proto *next;
+ struct node_proto *tail;
+};
+
+struct node_port {
+ u_int16_t port[2];
+ u_int8_t op;
+ struct node_port *next;
+ struct node_port *tail;
+};
+
+struct node_uid {
+ uid_t uid[2];
+ u_int8_t op;
+ struct node_uid *next;
+ struct node_uid *tail;
+};
+
+struct node_gid {
+ gid_t gid[2];
+ u_int8_t op;
+ struct node_gid *next;
+ struct node_gid *tail;
+};
+
+struct node_icmp {
+ uint16_t code;
+ uint16_t type;
+ u_int8_t proto;
+ struct node_icmp *next;
+ struct node_icmp *tail;
+};
+
+enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK,
+ PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN,
+ PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES,
+ PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK,
+ PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY,
+ PF_STATE_OPT_PFLOW, PF_STATE_OPT_ALLOW_RELATED };
+
+enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE };
+
+struct node_state_opt {
+ int type;
+ union {
+ u_int32_t max_states;
+ u_int32_t max_src_states;
+ u_int32_t max_src_conn;
+ struct {
+ u_int32_t limit;
+ u_int32_t seconds;
+ } max_src_conn_rate;
+ struct {
+ u_int8_t flush;
+ char tblname[PF_TABLE_NAME_SIZE];
+ } overload;
+ u_int32_t max_src_nodes;
+ u_int8_t src_track;
+ u_int32_t statelock;
+ struct {
+ int number;
+ u_int32_t seconds;
+ } timeout;
+ } data;
+ struct node_state_opt *next;
+ struct node_state_opt *tail;
+};
+
+struct peer {
+ struct node_host *host;
+ struct node_port *port;
+};
+
+static struct node_queue {
+ char queue[PF_QNAME_SIZE];
+ char parent[PF_QNAME_SIZE];
+ char ifname[IFNAMSIZ];
+ int scheduler;
+ struct node_queue *next;
+ struct node_queue *tail;
+} *queues = NULL;
+
+struct node_qassign {
+ char *qname;
+ char *pqname;
+};
+
+struct range {
+ int a;
+ int b;
+ int t;
+};
+
+static struct pool_opts {
+ int marker;
+#define POM_TYPE 0x01
+#define POM_STICKYADDRESS 0x02
+#define POM_ENDPI 0x04
+#define POM_IPV6NH 0x08
+ u_int8_t opts;
+ int type;
+ int staticport;
+ struct pf_poolhashkey *key;
+ struct pf_mape_portset mape;
+} pool_opts;
+
+struct redirspec {
+ struct node_host *host;
+ struct range rport;
+ struct pool_opts pool_opts;
+ sa_family_t af;
+ bool binat;
+};
+
+static struct filter_opts {
+ int marker;
+#define FOM_FLAGS 0x0001
+#define FOM_ICMP 0x0002
+#define FOM_TOS 0x0004
+#define FOM_KEEP 0x0008
+#define FOM_SRCTRACK 0x0010
+#define FOM_MINTTL 0x0020
+#define FOM_MAXMSS 0x0040
+#define FOM_AFTO 0x0080
+#define FOM_SETTOS 0x0100
+#define FOM_SCRUB_TCP 0x0200
+#define FOM_SETPRIO 0x0400
+#define FOM_ONCE 0x1000
+#define FOM_PRIO 0x2000
+#define FOM_SETDELAY 0x4000
+#define FOM_FRAGCACHE 0x8000 /* does not exist in OpenBSD */
+ struct node_uid *uid;
+ struct node_gid *gid;
+ struct node_if *rcv;
+ struct {
+ u_int8_t b1;
+ u_int8_t b2;
+ u_int16_t w;
+ u_int16_t w2;
+ } flags;
+ struct node_icmp *icmpspec;
+ u_int32_t tos;
+ u_int32_t prob;
+ u_int32_t ridentifier;
+ struct {
+ int action;
+ struct node_state_opt *options;
+ } keep;
+ int fragment;
+ int allowopts;
+ char *label[PF_RULE_MAX_LABEL_COUNT];
+ int labelcount;
+ struct node_qassign queues;
+ char *tag;
+ char *match_tag;
+ u_int8_t match_tag_not;
+ u_int16_t dnpipe;
+ u_int16_t dnrpipe;
+ u_int32_t free_flags;
+ u_int rtableid;
+ u_int8_t prio;
+ u_int8_t set_prio[2];
+ struct {
+ struct node_host *addr;
+ u_int16_t port;
+ } divert;
+ struct redirspec *nat;
+ struct redirspec *rdr;
+ /* new-style scrub opts */
+ int nodf;
+ int minttl;
+ int settos;
+ int randomid;
+ int max_mss;
+ struct {
+ uint32_t limit;
+ uint32_t seconds;
+ } pktrate;
+ int max_pkt_size;
+} filter_opts;
+
+static struct antispoof_opts {
+ char *label[PF_RULE_MAX_LABEL_COUNT];
+ int labelcount;
+ u_int32_t ridentifier;
+ u_int rtableid;
+} antispoof_opts;
+
+static struct scrub_opts {
+ int marker;
+ int nodf;
+ int minttl;
+ int maxmss;
+ int settos;
+ int fragcache;
+ int randomid;
+ int reassemble_tcp;
+ char *match_tag;
+ u_int8_t match_tag_not;
+ u_int rtableid;
+} scrub_opts;
+
+static struct queue_opts {
+ int marker;
+#define QOM_BWSPEC 0x01
+#define QOM_SCHEDULER 0x02
+#define QOM_PRIORITY 0x04
+#define QOM_TBRSIZE 0x08
+#define QOM_QLIMIT 0x10
+ struct node_queue_bw queue_bwspec;
+ struct node_queue_opt scheduler;
+ int priority;
+ unsigned int tbrsize;
+ int qlimit;
+} queue_opts;
+
+static struct table_opts {
+ int flags;
+ int init_addr;
+ struct node_tinithead init_nodes;
+} table_opts;
+
+static struct codel_opts codel_opts;
+static struct node_hfsc_opts hfsc_opts;
+static struct node_fairq_opts fairq_opts;
+static struct node_state_opt *keep_state_defaults = NULL;
+static struct pfctl_watermarks syncookie_opts;
+
+int validate_range(uint8_t, uint16_t, uint16_t);
+int disallow_table(struct node_host *, const char *);
+int disallow_urpf_failed(struct node_host *, const char *);
+int disallow_alias(struct node_host *, const char *);
+int rule_consistent(struct pfctl_rule *);
+int filter_consistent(struct pfctl_rule *);
+int nat_consistent(struct pfctl_rule *);
+int rdr_consistent(struct pfctl_rule *);
+int process_tabledef(char *, struct table_opts *, int);
+void expand_label_str(char *, size_t, const char *, const char *);
+void expand_label_if(const char *, char *, size_t, const char *);
+void expand_label_addr(const char *, char *, size_t, sa_family_t,
+ struct pf_rule_addr *);
+void expand_label_port(const char *, char *, size_t,
+ struct pf_rule_addr *);
+void expand_label_proto(const char *, char *, size_t, u_int8_t);
+void expand_label_nr(const char *, char *, size_t,
+ struct pfctl_rule *);
+void expand_eth_rule(struct pfctl_eth_rule *,
+ struct node_if *, struct node_etherproto *,
+ struct node_mac *, struct node_mac *,
+ struct node_host *, struct node_host *, const char *,
+ const char *);
+int apply_rdr_ports(struct pfctl_rule *r, struct pfctl_pool *, struct redirspec *);
+int apply_nat_ports(struct pfctl_pool *, struct redirspec *);
+int apply_redirspec(struct pfctl_pool *, struct redirspec *);
+int check_binat_redirspec(struct node_host *, struct pfctl_rule *, sa_family_t);
+void add_binat_rdr_rule(struct pfctl_rule *, struct redirspec *,
+ struct node_host *, struct pfctl_rule *, struct redirspec **,
+ struct node_host **);
+void expand_rule(struct pfctl_rule *, bool, struct node_if *,
+ struct redirspec *, struct redirspec *, struct redirspec *,
+ struct node_proto *, struct node_os *, struct node_host *,
+ struct node_port *, struct node_host *, struct node_port *,
+ struct node_uid *, struct node_gid *, struct node_if *,
+ struct node_icmp *);
+int expand_altq(struct pf_altq *, struct node_if *,
+ struct node_queue *, struct node_queue_bw bwspec,
+ struct node_queue_opt *);
+int expand_queue(struct pf_altq *, struct node_if *,
+ struct node_queue *, struct node_queue_bw,
+ struct node_queue_opt *);
+int expand_skip_interface(struct node_if *);
+
+int check_rulestate(int);
+int getservice(char *);
+int rule_label(struct pfctl_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]);
+int eth_rule_label(struct pfctl_eth_rule *, char *s[PF_RULE_MAX_LABEL_COUNT]);
+int rt_tableid_max(void);
+
+void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *);
+void mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *);
+void mv_tables(struct pfctl *, struct pfr_ktablehead *,
+ struct pfctl_anchor *, struct pfctl_anchor *);
+void decide_address_family(struct node_host *, sa_family_t *);
+void remove_invalid_hosts(struct node_host **, sa_family_t *);
+int invalid_redirect(struct node_host *, sa_family_t);
+u_int16_t parseicmpspec(char *, sa_family_t);
+int kw_casecmp(const void *, const void *);
+int map_tos(char *string, int *);
+int filteropts_to_rule(struct pfctl_rule *, struct filter_opts *);
+struct node_mac* node_mac_from_string(const char *);
+struct node_mac* node_mac_from_string_masklen(const char *, int);
+struct node_mac* node_mac_from_string_mask(const char *, const char *);
+static bool pfctl_setup_anchor(struct pfctl_rule *, struct pfctl *, char *);
+
+static TAILQ_HEAD(loadanchorshead, loadanchors)
+ loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead);
+
+struct loadanchors {
+ TAILQ_ENTRY(loadanchors) entries;
+ char *anchorname;
+ char *filename;
+};
+
+typedef struct {
+ union {
+ int64_t number;
+ double probability;
+ int i;
+ char *string;
+ u_int rtableid;
+ struct {
+ u_int8_t b1;
+ u_int8_t b2;
+ u_int16_t w;
+ u_int16_t w2;
+ } b;
+ struct range range;
+ struct node_if *interface;
+ struct node_proto *proto;
+ struct node_etherproto *etherproto;
+ struct node_icmp *icmp;
+ struct node_host *host;
+ struct node_os *os;
+ struct node_port *port;
+ struct node_uid *uid;
+ struct node_gid *gid;
+ struct node_state_opt *state_opt;
+ struct peer peer;
+ struct {
+ struct peer src, dst;
+ struct node_os *src_os;
+ } fromto;
+ struct {
+ struct node_mac *src;
+ struct node_mac *dst;
+ } etherfromto;
+ struct node_mac *mac;
+ struct {
+ struct node_mac *mac;
+ } etheraddr;
+ char *bridge_to;
+ struct {
+ struct redirspec *redirspec;
+ u_int8_t rt;
+ } route;
+ struct redirspec *redirspec;
+ struct {
+ int action;
+ struct node_state_opt *options;
+ } keep_state;
+ struct {
+ u_int8_t log;
+ u_int8_t logif;
+ u_int8_t quick;
+ } logquick;
+ struct {
+ int neg;
+ char *name;
+ } tagged;
+ struct pf_poolhashkey *hashkey;
+ struct node_queue *queue;
+ struct node_queue_opt queue_options;
+ struct node_queue_bw queue_bwspec;
+ struct node_qassign qassign;
+ struct filter_opts filter_opts;
+ struct antispoof_opts antispoof_opts;
+ struct queue_opts queue_opts;
+ struct scrub_opts scrub_opts;
+ struct table_opts table_opts;
+ struct pool_opts pool_opts;
+ struct node_hfsc_opts hfsc_opts;
+ struct node_fairq_opts fairq_opts;
+ struct codel_opts codel_opts;
+ struct pfctl_watermarks *watermarks;
+ } v;
+ int lineno;
+} YYSTYPE;
+
+#define PPORT_RANGE 1
+#define PPORT_STAR 2
+int parseport(char *, struct range *r, int);
+
+#define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \
+ (!((addr).iflags & PFI_AFLAG_NOALIAS) || \
+ !isdigit((addr).v.ifname[strlen((addr).v.ifname)-1])))
+
+%}
+
+%token PASS BLOCK MATCH SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS
+%token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE
+%token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF
+%token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL
+%token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE
+%token REASSEMBLE ANCHOR NATANCHOR RDRANCHOR BINATANCHOR
+%token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY
+%token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID
+%token ANTISPOOF FOR INCLUDE KEEPCOUNTERS SYNCOOKIES L3 MATCHES
+%token ETHER
+%token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY MAPEPORTSET
+%token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME
+%token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL
+%token DNPIPE DNQUEUE RIDENTIFIER
+%token LOAD RULESET_OPTIMIZATION PRIO ONCE
+%token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE
+%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED
+%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
+%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO
+%token BINATTO MAXPKTRATE MAXPKTSIZE IPV6NH
+%token <v.string> STRING
+%token <v.number> NUMBER
+%token <v.i> PORTBINARY
+%type <v.interface> interface if_list if_item_not if_item
+%type <v.number> number icmptype icmp6type uid gid
+%type <v.number> tos not yesno optnodf
+%type <v.probability> probability
+%type <v.i> no dir af fragcache optimizer syncookie_val
+%type <v.i> sourcetrack flush unaryop statelock
+%type <v.i> etherprotoval
+%type <v.b> action nataction natpasslog scrubaction
+%type <v.b> flags flag blockspec prio
+%type <v.range> portplain portstar portrange
+%type <v.hashkey> hashkey
+%type <v.proto> proto proto_list proto_item
+%type <v.number> protoval
+%type <v.icmp> icmpspec
+%type <v.icmp> icmp_list icmp_item
+%type <v.icmp> icmp6_list icmp6_item
+%type <v.number> reticmpspec reticmp6spec
+%type <v.fromto> fromto l3fromto
+%type <v.peer> ipportspec from to
+%type <v.host> ipspec toipspec xhost host dynaddr host_list
+%type <v.host> redir_host redir_host_list routespec
+%type <v.host> route_host route_host_list
+%type <v.os> os xos os_list
+%type <v.port> portspec port_list port_item
+%type <v.uid> uids uid_list uid_item
+%type <v.gid> gids gid_list gid_item
+%type <v.route> route
+%type <v.redirspec> no_port_redirspec port_redirspec route_redirspec
+%type <v.redirspec> binat_redirspec nat_redirspec
+%type <v.string> label stringall tag anchorname
+%type <v.string> string varstring numberstring
+%type <v.keep_state> keep
+%type <v.state_opt> state_opt_spec state_opt_list state_opt_item
+%type <v.logquick> logquick quick log logopts logopt
+%type <v.interface> antispoof_ifspc antispoof_iflst antispoof_if
+%type <v.qassign> qname etherqname
+%type <v.queue> qassign qassign_list qassign_item
+%type <v.queue_options> scheduler
+%type <v.number> cbqflags_list cbqflags_item
+%type <v.number> priqflags_list priqflags_item
+%type <v.hfsc_opts> hfscopts_list hfscopts_item hfsc_opts
+%type <v.fairq_opts> fairqopts_list fairqopts_item fairq_opts
+%type <v.codel_opts> codelopts_list codelopts_item codel_opts
+%type <v.queue_bwspec> bandwidth
+%type <v.filter_opts> filter_opts filter_opt filter_opts_l etherfilter_opts etherfilter_opt etherfilter_opts_l
+%type <v.filter_opts> filter_sets filter_set filter_sets_l
+%type <v.antispoof_opts> antispoof_opts antispoof_opt antispoof_opts_l
+%type <v.queue_opts> queue_opts queue_opt queue_opts_l
+%type <v.scrub_opts> scrub_opts scrub_opt scrub_opts_l
+%type <v.table_opts> table_opts table_opt table_opts_l
+%type <v.pool_opts> pool_opts pool_opt pool_opts_l
+%type <v.tagged> tagged
+%type <v.rtableid> rtable
+%type <v.watermarks> syncookie_opts
+%type <v.etherproto> etherproto etherproto_list etherproto_item
+%type <v.etherfromto> etherfromto
+%type <v.etheraddr> etherfrom etherto
+%type <v.bridge_to> bridge
+%type <v.mac> xmac mac mac_list macspec
+%%
+
+ruleset : /* empty */
+ | ruleset include '\n'
+ | ruleset '\n'
+ | ruleset option '\n'
+ | ruleset etherrule '\n'
+ | ruleset etheranchorrule '\n'
+ | ruleset scrubrule '\n'
+ | ruleset natrule '\n'
+ | ruleset binatrule '\n'
+ | ruleset pfrule '\n'
+ | ruleset anchorrule '\n'
+ | ruleset loadrule '\n'
+ | ruleset altqif '\n'
+ | ruleset queuespec '\n'
+ | ruleset varset '\n'
+ | ruleset antispoof '\n'
+ | ruleset tabledef '\n'
+ | '{' fakeanchor '}' '\n';
+ | ruleset error '\n' { file->errors++; }
+ ;
+
+include : INCLUDE STRING {
+ struct file *nfile;
+
+ if ((nfile = pushfile($2, 0)) == NULL) {
+ yyerror("failed to include file %s", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+
+ file = nfile;
+ lungetc('\n');
+ }
+ ;
+
+/*
+ * apply to previouslys specified rule: must be careful to note
+ * what that is: pf or nat or binat or rdr
+ */
+fakeanchor : fakeanchor '\n'
+ | fakeanchor anchorrule '\n'
+ | fakeanchor binatrule '\n'
+ | fakeanchor natrule '\n'
+ | fakeanchor pfrule '\n'
+ | fakeanchor error '\n'
+ ;
+
+optimizer : string {
+ if (!strcmp($1, "none"))
+ $$ = 0;
+ else if (!strcmp($1, "basic"))
+ $$ = PF_OPTIMIZE_BASIC;
+ else if (!strcmp($1, "profile"))
+ $$ = PF_OPTIMIZE_BASIC | PF_OPTIMIZE_PROFILE;
+ else {
+ yyerror("unknown ruleset-optimization %s", $1);
+ YYERROR;
+ }
+ }
+ ;
+
+optnodf : /* empty */ { $$ = 0; }
+ | NODF { $$ = 1; }
+ ;
+
+option : SET REASSEMBLE yesno optnodf {
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ pfctl_set_reassembly(pf, $3, $4);
+ }
+ | SET OPTIMIZATION STRING {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_set_optimization(pf, $3) != 0) {
+ yyerror("unknown optimization %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET RULESET_OPTIMIZATION optimizer {
+ if (!(pf->opts & PF_OPT_OPTIMIZE)) {
+ pf->opts |= PF_OPT_OPTIMIZE;
+ pf->optimize = $3;
+ }
+ }
+ | SET TIMEOUT timeout_spec
+ | SET TIMEOUT '{' optnl timeout_list '}'
+ | SET LIMIT limit_spec
+ | SET LIMIT '{' optnl limit_list '}'
+ | SET LOGINTERFACE stringall {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_set_logif(pf, $3) != 0) {
+ yyerror("error setting loginterface %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET HOSTID number {
+ if ($3 == 0 || $3 > UINT_MAX) {
+ yyerror("hostid must be non-zero");
+ YYERROR;
+ }
+ pfctl_set_hostid(pf, $3);
+ }
+ | SET BLOCKPOLICY DROP {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set block-policy drop\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ blockpolicy = PFRULE_DROP;
+ }
+ | SET BLOCKPOLICY RETURN {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set block-policy return\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ blockpolicy = PFRULE_RETURN;
+ }
+ | SET FAILPOLICY DROP {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set fail-policy drop\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ failpolicy = PFRULE_DROP;
+ }
+ | SET FAILPOLICY RETURN {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set fail-policy return\n");
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ failpolicy = PFRULE_RETURN;
+ }
+ | SET REQUIREORDER yesno {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set require-order %s\n",
+ $3 == 1 ? "yes" : "no");
+ require_order = $3;
+ }
+ | SET FINGERPRINTS STRING {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set fingerprints \"%s\"\n", $3);
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (!pf->anchor->name[0]) {
+ if (pfctl_file_fingerprints(pf->dev,
+ pf->opts, $3)) {
+ yyerror("error loading "
+ "fingerprints %s", $3);
+ free($3);
+ YYERROR;
+ }
+ }
+ free($3);
+ }
+ | SET STATEPOLICY statelock {
+ if (pf->opts & PF_OPT_VERBOSE)
+ switch ($3) {
+ case 0:
+ printf("set state-policy floating\n");
+ break;
+ case PFRULE_IFBOUND:
+ printf("set state-policy if-bound\n");
+ break;
+ }
+ default_statelock = $3;
+ }
+ | SET DEBUG STRING {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($3);
+ YYERROR;
+ }
+ if (pfctl_do_set_debug(pf, $3) != 0) {
+ yyerror("error setting debuglevel %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ | SET SKIP interface {
+ if (expand_skip_interface($3) != 0) {
+ yyerror("error setting skip interface(s)");
+ YYERROR;
+ }
+ }
+ | SET STATEDEFAULTS state_opt_list {
+ if (keep_state_defaults != NULL) {
+ yyerror("cannot redefine state-defaults");
+ YYERROR;
+ }
+ keep_state_defaults = $3;
+ }
+ | SET KEEPCOUNTERS {
+ pf->keep_counters = true;
+ }
+ | SET SYNCOOKIES syncookie_val syncookie_opts {
+ if (pfctl_cfg_syncookies(pf, $3, $4)) {
+ yyerror("error setting syncookies");
+ YYERROR;
+ }
+ }
+ ;
+
+syncookie_val : STRING {
+ if (!strcmp($1, "never"))
+ $$ = PFCTL_SYNCOOKIES_NEVER;
+ else if (!strcmp($1, "adaptive"))
+ $$ = PFCTL_SYNCOOKIES_ADAPTIVE;
+ else if (!strcmp($1, "always"))
+ $$ = PFCTL_SYNCOOKIES_ALWAYS;
+ else {
+ yyerror("illegal value for syncookies");
+ YYERROR;
+ }
+ }
+ ;
+syncookie_opts : /* empty */ { $$ = NULL; }
+ | {
+ memset(&syncookie_opts, 0, sizeof(syncookie_opts));
+ } '(' syncookie_opt_l ')' { $$ = &syncookie_opts; }
+ ;
+
+syncookie_opt_l : syncookie_opt_l comma syncookie_opt
+ | syncookie_opt
+ ;
+
+syncookie_opt : STRING STRING {
+ double val;
+ char *cp;
+
+ val = strtod($2, &cp);
+ if (cp == NULL || strcmp(cp, "%"))
+ YYERROR;
+ if (val <= 0 || val > 100) {
+ yyerror("illegal percentage value");
+ YYERROR;
+ }
+ if (!strcmp($1, "start")) {
+ syncookie_opts.hi = val;
+ } else if (!strcmp($1, "end")) {
+ syncookie_opts.lo = val;
+ } else {
+ yyerror("illegal syncookie option");
+ YYERROR;
+ }
+ }
+ ;
+
+stringall : STRING { $$ = $1; }
+ | ALL {
+ if (($$ = strdup("all")) == NULL) {
+ err(1, "stringall: strdup");
+ }
+ }
+ ;
+
+string : STRING string {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1)
+ err(1, "string: asprintf");
+ free($1);
+ free($2);
+ }
+ | STRING
+ ;
+
+varstring : numberstring varstring {
+ if (asprintf(&$$, "%s %s", $1, $2) == -1)
+ err(1, "string: asprintf");
+ free($1);
+ free($2);
+ }
+ | numberstring
+ ;
+
+numberstring : NUMBER {
+ char *s;
+ if (asprintf(&s, "%lld", (long long)$1) == -1) {
+ yyerror("string: asprintf");
+ YYERROR;
+ }
+ $$ = s;
+ }
+ | STRING
+ ;
+
+varset : STRING '=' varstring {
+ char *s = $1;
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("%s = \"%s\"\n", $1, $3);
+ while (*s++) {
+ if (isspace((unsigned char)*s)) {
+ yyerror("macro name cannot contain "
+ "whitespace");
+ free($1);
+ free($3);
+ YYERROR;
+ }
+ }
+ if (symset($1, $3, 0) == -1)
+ err(1, "cannot store variable %s", $1);
+ free($1);
+ free($3);
+ }
+ ;
+
+anchorname : STRING {
+ if ($1[0] == '\0') {
+ free($1);
+ yyerror("anchor name must not be empty");
+ YYERROR;
+ }
+ if (strlen(pf->anchor->path) + 1 +
+ strlen($1) >= PATH_MAX) {
+ free($1);
+ yyerror("anchor name is longer than %u",
+ PATH_MAX - 1);
+ YYERROR;
+ }
+ if ($1[0] == '_' || strstr($1, "/_") != NULL) {
+ free($1);
+ yyerror("anchor names beginning with '_' "
+ "are reserved for internal use");
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+pfa_anchorlist : /* empty */
+ | pfa_anchorlist '\n'
+ | pfa_anchorlist tabledef '\n'
+ | pfa_anchorlist pfrule '\n'
+ | pfa_anchorlist anchorrule '\n'
+ | pfa_anchorlist include '\n'
+ ;
+
+pfa_anchor : '{'
+ {
+ char ta[PF_ANCHOR_NAME_SIZE];
+ struct pfctl_ruleset *rs;
+
+ /* stepping into a brace anchor */
+ if (pf->asd >= PFCTL_ANCHOR_STACK_DEPTH)
+ errx(1, "pfa_anchor: anchors too deep");
+ pf->asd++;
+ pf->bn++;
+
+ /*
+ * Anchor contents are parsed before the anchor rule
+ * production completes, so we don't know the real
+ * location yet. Create a holding ruleset in the root;
+ * contents will be moved afterwards.
+ */
+ snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn);
+ rs = pf_find_or_create_ruleset(ta);
+ if (rs == NULL)
+ err(1, "pfa_anchor: pf_find_or_create_ruleset (%s)", ta);
+ pf->astack[pf->asd] = rs->anchor;
+ pf->anchor = rs->anchor;
+ } '\n' pfa_anchorlist '}'
+ {
+ pf->alast = pf->anchor;
+ pf->asd--;
+ pf->anchor = pf->astack[pf->asd];
+ }
+ | /* empty */
+ ;
+
+anchorrule : ANCHOR anchorname dir quick interface af proto fromto
+ filter_opts pfa_anchor
+ {
+ struct pfctl_rule r;
+ struct node_proto *proto;
+
+ if (check_rulestate(PFCTL_STATE_FILTER)) {
+ if ($2)
+ free($2);
+ YYERROR;
+ }
+
+ pfctl_init_rule(&r);
+ if (! pfctl_setup_anchor(&r, pf, $2))
+ YYERROR;
+
+ r.direction = $3;
+ r.quick = $4.quick;
+ r.af = $6;
+
+ if ($9.flags.b1 || $9.flags.b2 || $8.src_os) {
+ for (proto = $7; proto != NULL &&
+ proto->proto != IPPROTO_TCP;
+ proto = proto->next)
+ ; /* nothing */
+ if (proto == NULL && $7 != NULL) {
+ if ($9.flags.b1 || $9.flags.b2)
+ yyerror(
+ "flags only apply to tcp");
+ if ($8.src_os)
+ yyerror(
+ "OS fingerprinting only "
+ "applies to tcp");
+ YYERROR;
+ }
+ }
+
+ if (filteropts_to_rule(&r, &$9))
+ YYERROR;
+
+ if ($9.keep.action) {
+ yyerror("cannot specify state handling "
+ "on anchors");
+ YYERROR;
+ }
+
+ decide_address_family($8.src.host, &r.af);
+ decide_address_family($8.dst.host, &r.af);
+
+ expand_rule(&r, false, $5, NULL, NULL, NULL,
+ $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host,
+ $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec);
+ free($2);
+ pf->astack[pf->asd + 1] = NULL;
+ }
+ | NATANCHOR string interface af proto fromto rtable {
+ struct pfctl_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ pfctl_init_rule(&r);
+ if (! pfctl_setup_anchor(&r, pf, $2))
+ YYERROR;
+
+ r.action = PF_NAT;
+ r.af = $4;
+ r.rtableid = $7;
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ expand_rule(&r, false, $3, NULL, NULL, NULL,
+ $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host,
+ $6.dst.port, 0, 0, 0, 0);
+ free($2);
+ }
+ | RDRANCHOR string interface af proto fromto rtable {
+ struct pfctl_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ pfctl_init_rule(&r);
+ if (! pfctl_setup_anchor(&r, pf, $2))
+ YYERROR;
+
+ r.action = PF_RDR;
+ r.af = $4;
+ r.rtableid = $7;
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ if ($6.src.port != NULL) {
+ yyerror("source port parameter not supported"
+ " in rdr-anchor");
+ YYERROR;
+ }
+ if ($6.dst.port != NULL) {
+ if ($6.dst.port->next != NULL) {
+ yyerror("destination port list "
+ "expansion not supported in "
+ "rdr-anchor");
+ YYERROR;
+ } else if ($6.dst.port->op != PF_OP_EQ) {
+ yyerror("destination port operators"
+ " not supported in rdr-anchor");
+ YYERROR;
+ }
+ r.dst.port[0] = $6.dst.port->port[0];
+ r.dst.port[1] = $6.dst.port->port[1];
+ r.dst.port_op = $6.dst.port->op;
+ }
+
+ expand_rule(&r, false, $3, NULL, NULL, NULL,
+ $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host,
+ $6.dst.port, 0, 0, 0, 0);
+ free($2);
+ }
+ | BINATANCHOR string interface af proto fromto rtable {
+ struct pfctl_rule r;
+
+ if (check_rulestate(PFCTL_STATE_NAT)) {
+ free($2);
+ YYERROR;
+ }
+
+ pfctl_init_rule(&r);
+ if (! pfctl_setup_anchor(&r, pf, $2))
+ YYERROR;
+
+ r.action = PF_BINAT;
+ r.af = $4;
+ r.rtableid = $7;
+ if ($5 != NULL) {
+ if ($5->next != NULL) {
+ yyerror("proto list expansion"
+ " not supported in binat-anchor");
+ YYERROR;
+ }
+ r.proto = $5->proto;
+ free($5);
+ }
+
+ if ($6.src.host != NULL || $6.src.port != NULL ||
+ $6.dst.host != NULL || $6.dst.port != NULL) {
+ yyerror("fromto parameter not supported"
+ " in binat-anchor");
+ YYERROR;
+ }
+
+ decide_address_family($6.src.host, &r.af);
+ decide_address_family($6.dst.host, &r.af);
+
+ pfctl_append_rule(pf, &r);
+ free($2);
+ }
+ ;
+
+loadrule : LOAD ANCHOR anchorname FROM string {
+ struct loadanchors *loadanchor;
+
+ if ($3 == NULL) {
+ yyerror("anchor name is missing");
+ YYERROR;
+ }
+ loadanchor = calloc(1, sizeof(struct loadanchors));
+ if (loadanchor == NULL)
+ err(1, "loadrule: calloc");
+ if ((loadanchor->anchorname = malloc(MAXPATHLEN)) ==
+ NULL)
+ err(1, "loadrule: malloc");
+ if (pf->anchor->name[0])
+ snprintf(loadanchor->anchorname, MAXPATHLEN,
+ "%s/%s", pf->anchor->path, $3);
+ else
+ strlcpy(loadanchor->anchorname, $3, MAXPATHLEN);
+ if ((loadanchor->filename = strdup($5)) == NULL)
+ err(1, "loadrule: strdup");
+
+ TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor,
+ entries);
+
+ free($3);
+ free($5);
+ };
+
+scrubaction : no SCRUB {
+ $$.b2 = $$.w = 0;
+ if ($1)
+ $$.b1 = PF_NOSCRUB;
+ else
+ $$.b1 = PF_SCRUB;
+ }
+ ;
+
+etherrule : ETHER action dir quick interface bridge etherproto etherfromto l3fromto etherfilter_opts
+ {
+ struct pfctl_eth_rule r;
+
+ bzero(&r, sizeof(r));
+
+ if (check_rulestate(PFCTL_STATE_ETHER))
+ YYERROR;
+
+ r.action = $2.b1;
+ r.direction = $3;
+ r.quick = $4.quick;
+ if ($10.tag != NULL)
+ strlcpy(r.tagname, $10.tag, sizeof(r.tagname));
+ if ($10.match_tag)
+ if (strlcpy(r.match_tagname, $10.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $10.match_tag_not;
+ if ($10.queues.qname != NULL)
+ strlcpy(r.qname, $10.queues.qname, sizeof(r.qname));
+ r.dnpipe = $10.dnpipe;
+ r.dnflags = $10.free_flags;
+ if (eth_rule_label(&r, $10.label))
+ YYERROR;
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++)
+ free($10.label[i]);
+ r.ridentifier = $10.ridentifier;
+
+ expand_eth_rule(&r, $5, $7, $8.src, $8.dst,
+ $9.src.host, $9.dst.host, $6, "");
+ }
+ ;
+
+etherpfa_anchorlist : /* empty */
+ | etherpfa_anchorlist '\n'
+ | etherpfa_anchorlist etherrule '\n'
+ | etherpfa_anchorlist etheranchorrule '\n'
+ ;
+
+etherpfa_anchor : '{'
+ {
+ char ta[PF_ANCHOR_NAME_SIZE];
+ struct pfctl_eth_ruleset *rs;
+
+ /* steping into a brace anchor */
+ if (pf->asd >= PFCTL_ANCHOR_STACK_DEPTH)
+ errx(1, "pfa_anchor: anchors too deep");
+ pf->asd++;
+ pf->bn++;
+
+ /* create a holding ruleset in the root */
+ snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn);
+ rs = pf_find_or_create_eth_ruleset(ta);
+ if (rs == NULL)
+ err(1, "etherpfa_anchor: pf_find_or_create_eth_ruleset");
+ pf->eastack[pf->asd] = rs->anchor;
+ pf->eanchor = rs->anchor;
+ } '\n' etherpfa_anchorlist '}'
+ {
+ pf->ealast = pf->eanchor;
+ pf->asd--;
+ pf->eanchor = pf->eastack[pf->asd];
+ }
+ | /* empty */
+ ;
+
+etheranchorrule : ETHER ANCHOR anchorname dir quick interface etherproto etherfromto l3fromto etherpfa_anchor
+ {
+ struct pfctl_eth_rule r;
+
+ if (check_rulestate(PFCTL_STATE_ETHER)) {
+ free($3);
+ YYERROR;
+ }
+
+ if ($3 && ($3[0] == '_' || strstr($3, "/_") != NULL)) {
+ free($3);
+ yyerror("anchor names beginning with '_' "
+ "are reserved for internal use");
+ YYERROR;
+ }
+
+ memset(&r, 0, sizeof(r));
+ if (pf->eastack[pf->asd + 1]) {
+ if ($3 && strchr($3, '/') != NULL) {
+ free($3);
+ yyerror("anchor paths containing '/' "
+ "cannot be used for inline anchors.");
+ YYERROR;
+ }
+
+ /* Move inline rules into relative location. */
+ pfctl_eth_anchor_setup(pf, &r,
+ &pf->eastack[pf->asd]->ruleset,
+ $3 ? $3 : pf->ealast->name);
+ if (r.anchor == NULL)
+ err(1, "etheranchorrule: unable to "
+ "create ruleset");
+
+ if (pf->ealast != r.anchor) {
+ if (r.anchor->match) {
+ yyerror("inline anchor '%s' "
+ "already exists",
+ r.anchor->name);
+ YYERROR;
+ }
+ mv_eth_rules(&pf->ealast->ruleset,
+ &r.anchor->ruleset);
+ }
+ pf_remove_if_empty_eth_ruleset(&pf->ealast->ruleset);
+ pf->ealast = r.anchor;
+ } else {
+ if (!$3) {
+ yyerror("anchors without explicit "
+ "rules must specify a name");
+ YYERROR;
+ }
+ }
+
+ r.direction = $4;
+ r.quick = $5.quick;
+
+ expand_eth_rule(&r, $6, $7, $8.src, $8.dst,
+ $9.src.host, $9.dst.host, NULL,
+ pf->eastack[pf->asd + 1] ? pf->ealast->name : $3);
+
+ free($3);
+ pf->eastack[pf->asd + 1] = NULL;
+ }
+ ;
+
+etherfilter_opts : {
+ bzero(&filter_opts, sizeof filter_opts);
+ }
+ etherfilter_opts_l
+ { $$ = filter_opts; }
+ | /* empty */ {
+ bzero(&filter_opts, sizeof filter_opts);
+ $$ = filter_opts;
+ }
+ ;
+
+etherfilter_opts_l : etherfilter_opts_l etherfilter_opt
+ | etherfilter_opt
+
+etherfilter_opt : etherqname {
+ if (filter_opts.queues.qname) {
+ yyerror("queue cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.queues = $1;
+ }
+ | RIDENTIFIER number {
+ filter_opts.ridentifier = $2;
+ }
+ | label {
+ if (filter_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) {
+ yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT);
+ YYERROR;
+ }
+ filter_opts.label[filter_opts.labelcount++] = $1;
+ }
+ | TAG string {
+ filter_opts.tag = $2;
+ }
+ | not TAGGED string {
+ filter_opts.match_tag = $3;
+ filter_opts.match_tag_not = $1;
+ }
+ | DNPIPE number {
+ filter_opts.dnpipe = $2;
+ filter_opts.free_flags |= PFRULE_DN_IS_PIPE;
+ }
+ | DNQUEUE number {
+ filter_opts.dnpipe = $2;
+ filter_opts.free_flags |= PFRULE_DN_IS_QUEUE;
+ }
+ ;
+
+bridge : /* empty */ {
+ $$ = NULL;
+ }
+ | BRIDGE_TO STRING {
+ $$ = strdup($2);
+ }
+ ;
+
+scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts
+ {
+ struct pfctl_rule r;
+
+ if (check_rulestate(PFCTL_STATE_SCRUB))
+ YYERROR;
+
+ pfctl_init_rule(&r);
+
+ r.action = $1.b1;
+ r.direction = $2;
+
+ r.log = $3.log;
+ r.logif = $3.logif;
+ if ($3.quick) {
+ yyerror("scrub rules do not support 'quick'");
+ YYERROR;
+ }
+
+ r.af = $5;
+ if ($8.nodf)
+ r.rule_flag |= PFRULE_NODF;
+ if ($8.randomid)
+ r.rule_flag |= PFRULE_RANDOMID;
+ if ($8.reassemble_tcp) {
+ if (r.direction != PF_INOUT) {
+ yyerror("reassemble tcp rules can not "
+ "specify direction");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_REASSEMBLE_TCP;
+ }
+ if ($8.minttl)
+ r.min_ttl = $8.minttl;
+ if ($8.maxmss)
+ r.max_mss = $8.maxmss;
+ if ($8.marker & FOM_SETTOS) {
+ r.rule_flag |= PFRULE_SET_TOS;
+ r.set_tos = $8.settos;
+ }
+ if ($8.fragcache)
+ r.rule_flag |= $8.fragcache;
+ if ($8.match_tag)
+ if (strlcpy(r.match_tagname, $8.match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $8.match_tag_not;
+ r.rtableid = $8.rtableid;
+
+ expand_rule(&r, false, $4, NULL, NULL, NULL,
+ $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host,
+ $7.dst.port, NULL, NULL, NULL, NULL);
+ }
+ ;
+
+scrub_opts : {
+ bzero(&scrub_opts, sizeof scrub_opts);
+ scrub_opts.rtableid = -1;
+ }
+ scrub_opts_l
+ { $$ = scrub_opts; }
+ | /* empty */ {
+ bzero(&scrub_opts, sizeof scrub_opts);
+ scrub_opts.rtableid = -1;
+ $$ = scrub_opts;
+ }
+ ;
+
+scrub_opts_l : scrub_opts_l comma scrub_opt
+ | scrub_opt
+ ;
+
+scrub_opt : NODF {
+ if (scrub_opts.nodf) {
+ yyerror("no-df cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.nodf = 1;
+ }
+ | MINTTL NUMBER {
+ if (scrub_opts.marker & FOM_MINTTL) {
+ yyerror("min-ttl cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 255) {
+ yyerror("illegal min-ttl value %d", $2);
+ YYERROR;
+ }
+ scrub_opts.marker |= FOM_MINTTL;
+ scrub_opts.minttl = $2;
+ }
+ | MAXMSS NUMBER {
+ if (scrub_opts.marker & FOM_MAXMSS) {
+ yyerror("max-mss cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 65535) {
+ yyerror("illegal max-mss value %d", $2);
+ YYERROR;
+ }
+ scrub_opts.marker |= FOM_MAXMSS;
+ scrub_opts.maxmss = $2;
+ }
+ | SETTOS tos {
+ if (scrub_opts.marker & FOM_SETTOS) {
+ yyerror("set-tos cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.marker |= FOM_SETTOS;
+ scrub_opts.settos = $2;
+ }
+ | fragcache {
+ if (scrub_opts.marker & FOM_FRAGCACHE) {
+ yyerror("fragcache cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.marker |= FOM_FRAGCACHE;
+ scrub_opts.fragcache = $1;
+ }
+ | REASSEMBLE STRING {
+ if (strcasecmp($2, "tcp") != 0) {
+ yyerror("scrub reassemble supports only tcp, "
+ "not '%s'", $2);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ if (scrub_opts.reassemble_tcp) {
+ yyerror("reassemble tcp cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.reassemble_tcp = 1;
+ }
+ | RANDOMID {
+ if (scrub_opts.randomid) {
+ yyerror("random-id cannot be respecified");
+ YYERROR;
+ }
+ scrub_opts.randomid = 1;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ scrub_opts.rtableid = $2;
+ }
+ | not TAGGED string {
+ scrub_opts.match_tag = $3;
+ scrub_opts.match_tag_not = $1;
+ }
+ ;
+
+fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ }
+ | FRAGMENT NO REASSEMBLE { $$ = PFRULE_FRAGMENT_NOREASS; }
+ ;
+
+antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts {
+ struct pfctl_rule r;
+ struct node_host *h = NULL, *hh;
+ struct node_if *i, *j;
+
+ if (check_rulestate(PFCTL_STATE_FILTER))
+ YYERROR;
+
+ for (i = $3; i; i = i->next) {
+ pfctl_init_rule(&r);
+
+ r.action = PF_DROP;
+ r.direction = PF_IN;
+ r.log = $2.log;
+ r.logif = $2.logif;
+ r.quick = $2.quick;
+ r.af = $4;
+ r.ridentifier = $5.ridentifier;
+ if (rule_label(&r, $5.label))
+ YYERROR;
+ r.rtableid = $5.rtableid;
+ j = calloc(1, sizeof(struct node_if));
+ if (j == NULL)
+ err(1, "antispoof: calloc");
+ if (strlcpy(j->ifname, i->ifname,
+ sizeof(j->ifname)) >= sizeof(j->ifname)) {
+ free(j);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ j->not = 1;
+ if (i->dynamic) {
+ h = calloc(1, sizeof(*h));
+ if (h == NULL)
+ err(1, "address: calloc");
+ h->addr.type = PF_ADDR_DYNIFTL;
+ set_ipmask(h, 128);
+ if (strlcpy(h->addr.v.ifname, i->ifname,
+ sizeof(h->addr.v.ifname)) >=
+ sizeof(h->addr.v.ifname)) {
+ free(h);
+ yyerror(
+ "interface name too long");
+ YYERROR;
+ }
+ hh = malloc(sizeof(*hh));
+ if (hh == NULL)
+ err(1, "address: malloc");
+ bcopy(h, hh, sizeof(*hh));
+ h->addr.iflags = PFI_AFLAG_NETWORK;
+ } else {
+ h = ifa_lookup(j->ifname,
+ PFI_AFLAG_NETWORK);
+ hh = NULL;
+ }
+
+ if (h != NULL)
+ expand_rule(&r, false, j, NULL, NULL,
+ NULL, NULL, NULL, h, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL);
+
+ if ((i->ifa_flags & IFF_LOOPBACK) == 0) {
+ bzero(&r, sizeof(r));
+
+ r.action = PF_DROP;
+ r.direction = PF_IN;
+ r.log = $2.log;
+ r.logif = $2.logif;
+ r.quick = $2.quick;
+ r.af = $4;
+ r.ridentifier = $5.ridentifier;
+ if (rule_label(&r, $5.label))
+ YYERROR;
+ r.rtableid = $5.rtableid;
+ if (hh != NULL)
+ h = hh;
+ else
+ h = ifa_lookup(i->ifname, 0);
+ if (h != NULL)
+ expand_rule(&r, false, NULL,
+ NULL, NULL, NULL, NULL,
+ NULL, h, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL);
+ } else
+ free(hh);
+ }
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++)
+ free($5.label[i]);
+ }
+ ;
+
+antispoof_ifspc : FOR antispoof_if { $$ = $2; }
+ | FOR '{' optnl antispoof_iflst '}' { $$ = $4; }
+ ;
+
+antispoof_iflst : antispoof_if optnl { $$ = $1; }
+ | antispoof_iflst comma antispoof_if optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+antispoof_if : if_item { $$ = $1; }
+ | '(' if_item ')' {
+ $2->dynamic = 1;
+ $$ = $2;
+ }
+ ;
+
+antispoof_opts : {
+ bzero(&antispoof_opts, sizeof antispoof_opts);
+ antispoof_opts.rtableid = -1;
+ }
+ antispoof_opts_l
+ { $$ = antispoof_opts; }
+ | /* empty */ {
+ bzero(&antispoof_opts, sizeof antispoof_opts);
+ antispoof_opts.rtableid = -1;
+ $$ = antispoof_opts;
+ }
+ ;
+
+antispoof_opts_l : antispoof_opts_l antispoof_opt
+ | antispoof_opt
+ ;
+
+antispoof_opt : label {
+ if (antispoof_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) {
+ yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT);
+ YYERROR;
+ }
+ antispoof_opts.label[antispoof_opts.labelcount++] = $1;
+ }
+ | RIDENTIFIER number {
+ antispoof_opts.ridentifier = $2;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ antispoof_opts.rtableid = $2;
+ }
+ ;
+
+not : '!' { $$ = 1; }
+ | /* empty */ { $$ = 0; }
+ ;
+
+tabledef : TABLE '<' STRING '>' table_opts {
+ struct node_host *h, *nh;
+ struct node_tinit *ti, *nti;
+
+ if (strlen($3) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name too long, max %d chars",
+ PF_TABLE_NAME_SIZE - 1);
+ free($3);
+ YYERROR;
+ }
+ if (pf->loadopt & PFCTL_FLAG_TABLE)
+ if (process_tabledef($3, &$5, pf->opts)) {
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ for (ti = SIMPLEQ_FIRST(&$5.init_nodes);
+ ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) {
+ if (ti->file)
+ free(ti->file);
+ for (h = ti->host; h != NULL; h = nh) {
+ nh = h->next;
+ free(h);
+ }
+ nti = SIMPLEQ_NEXT(ti, entries);
+ free(ti);
+ }
+ }
+ ;
+
+table_opts : {
+ bzero(&table_opts, sizeof table_opts);
+ SIMPLEQ_INIT(&table_opts.init_nodes);
+ }
+ table_opts_l
+ { $$ = table_opts; }
+ | /* empty */
+ {
+ bzero(&table_opts, sizeof table_opts);
+ SIMPLEQ_INIT(&table_opts.init_nodes);
+ $$ = table_opts;
+ }
+ ;
+
+table_opts_l : table_opts_l table_opt
+ | table_opt
+ ;
+
+table_opt : STRING {
+ if (!strcmp($1, "const"))
+ table_opts.flags |= PFR_TFLAG_CONST;
+ else if (!strcmp($1, "persist"))
+ table_opts.flags |= PFR_TFLAG_PERSIST;
+ else if (!strcmp($1, "counters"))
+ table_opts.flags |= PFR_TFLAG_COUNTERS;
+ else {
+ yyerror("invalid table option '%s'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | '{' optnl '}' { table_opts.init_addr = 1; }
+ | '{' optnl host_list '}' {
+ struct node_host *n;
+ struct node_tinit *ti;
+
+ for (n = $3; n != NULL; n = n->next) {
+ switch (n->addr.type) {
+ case PF_ADDR_ADDRMASK:
+ continue; /* ok */
+ case PF_ADDR_RANGE:
+ yyerror("address ranges are not "
+ "permitted inside tables");
+ break;
+ case PF_ADDR_DYNIFTL:
+ yyerror("dynamic addresses are not "
+ "permitted inside tables");
+ break;
+ case PF_ADDR_TABLE:
+ yyerror("tables cannot contain tables");
+ break;
+ case PF_ADDR_NOROUTE:
+ yyerror("\"no-route\" is not permitted "
+ "inside tables");
+ break;
+ case PF_ADDR_URPFFAILED:
+ yyerror("\"urpf-failed\" is not "
+ "permitted inside tables");
+ break;
+ default:
+ yyerror("unknown address type %d",
+ n->addr.type);
+ }
+ YYERROR;
+ }
+ if (!(ti = calloc(1, sizeof(*ti))))
+ err(1, "table_opt: calloc");
+ ti->host = $3;
+ SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti,
+ entries);
+ table_opts.init_addr = 1;
+ }
+ | FILENAME STRING {
+ struct node_tinit *ti;
+
+ if (!(ti = calloc(1, sizeof(*ti))))
+ err(1, "table_opt: calloc");
+ ti->file = $2;
+ SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti,
+ entries);
+ table_opts.init_addr = 1;
+ }
+ ;
+
+altqif : ALTQ interface queue_opts QUEUE qassign {
+ struct pf_altq a;
+
+ if (check_rulestate(PFCTL_STATE_QUEUE))
+ YYERROR;
+
+ memset(&a, 0, sizeof(a));
+ if ($3.scheduler.qtype == ALTQT_NONE) {
+ yyerror("no scheduler specified!");
+ YYERROR;
+ }
+ a.scheduler = $3.scheduler.qtype;
+ a.qlimit = $3.qlimit;
+ a.tbrsize = $3.tbrsize;
+ if ($5 == NULL && $3.scheduler.qtype != ALTQT_CODEL) {
+ yyerror("no child queues specified");
+ YYERROR;
+ }
+ if (expand_altq(&a, $2, $5, $3.queue_bwspec,
+ &$3.scheduler))
+ YYERROR;
+ }
+ ;
+
+queuespec : QUEUE STRING interface queue_opts qassign {
+ struct pf_altq a;
+
+ if (check_rulestate(PFCTL_STATE_QUEUE)) {
+ free($2);
+ YYERROR;
+ }
+
+ memset(&a, 0, sizeof(a));
+
+ if (strlcpy(a.qname, $2, sizeof(a.qname)) >=
+ sizeof(a.qname)) {
+ yyerror("queue name too long (max "
+ "%d chars)", PF_QNAME_SIZE-1);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ if ($4.tbrsize) {
+ yyerror("cannot specify tbrsize for queue");
+ YYERROR;
+ }
+ if ($4.priority > 255) {
+ yyerror("priority out of range: max 255");
+ YYERROR;
+ }
+ a.priority = $4.priority;
+ a.qlimit = $4.qlimit;
+ a.scheduler = $4.scheduler.qtype;
+ if (expand_queue(&a, $3, $5, $4.queue_bwspec,
+ &$4.scheduler)) {
+ yyerror("errors in queue definition");
+ YYERROR;
+ }
+ }
+ ;
+
+queue_opts : {
+ bzero(&queue_opts, sizeof queue_opts);
+ queue_opts.priority = DEFAULT_PRIORITY;
+ queue_opts.qlimit = DEFAULT_QLIMIT;
+ queue_opts.scheduler.qtype = ALTQT_NONE;
+ queue_opts.queue_bwspec.bw_percent = 100;
+ }
+ queue_opts_l
+ { $$ = queue_opts; }
+ | /* empty */ {
+ bzero(&queue_opts, sizeof queue_opts);
+ queue_opts.priority = DEFAULT_PRIORITY;
+ queue_opts.qlimit = DEFAULT_QLIMIT;
+ queue_opts.scheduler.qtype = ALTQT_NONE;
+ queue_opts.queue_bwspec.bw_percent = 100;
+ $$ = queue_opts;
+ }
+ ;
+
+queue_opts_l : queue_opts_l queue_opt
+ | queue_opt
+ ;
+
+queue_opt : BANDWIDTH bandwidth {
+ if (queue_opts.marker & QOM_BWSPEC) {
+ yyerror("bandwidth cannot be respecified");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_BWSPEC;
+ queue_opts.queue_bwspec = $2;
+ }
+ | PRIORITY NUMBER {
+ if (queue_opts.marker & QOM_PRIORITY) {
+ yyerror("priority cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 255) {
+ yyerror("priority out of range: max 255");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_PRIORITY;
+ queue_opts.priority = $2;
+ }
+ | QLIMIT NUMBER {
+ if (queue_opts.marker & QOM_QLIMIT) {
+ yyerror("qlimit cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > 65535) {
+ yyerror("qlimit out of range: max 65535");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_QLIMIT;
+ queue_opts.qlimit = $2;
+ }
+ | scheduler {
+ if (queue_opts.marker & QOM_SCHEDULER) {
+ yyerror("scheduler cannot be respecified");
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_SCHEDULER;
+ queue_opts.scheduler = $1;
+ }
+ | TBRSIZE NUMBER {
+ if (queue_opts.marker & QOM_TBRSIZE) {
+ yyerror("tbrsize cannot be respecified");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("tbrsize too big: max %u", UINT_MAX);
+ YYERROR;
+ }
+ queue_opts.marker |= QOM_TBRSIZE;
+ queue_opts.tbrsize = $2;
+ }
+ ;
+
+bandwidth : STRING {
+ double bps;
+ char *cp;
+
+ $$.bw_percent = 0;
+
+ bps = strtod($1, &cp);
+ if (cp != NULL) {
+ if (strlen(cp) > 1) {
+ char *cu = cp + 1;
+ if (!strcmp(cu, "Bit") ||
+ !strcmp(cu, "B") ||
+ !strcmp(cu, "bit") ||
+ !strcmp(cu, "b")) {
+ *cu = 0;
+ }
+ }
+ if (!strcmp(cp, "b"))
+ ; /* nothing */
+ else if (!strcmp(cp, "K"))
+ bps *= 1000;
+ else if (!strcmp(cp, "M"))
+ bps *= 1000 * 1000;
+ else if (!strcmp(cp, "G"))
+ bps *= 1000 * 1000 * 1000;
+ else if (!strcmp(cp, "%")) {
+ if (bps < 0 || bps > 100) {
+ yyerror("bandwidth spec "
+ "out of range");
+ free($1);
+ YYERROR;
+ }
+ $$.bw_percent = bps;
+ bps = 0;
+ } else {
+ yyerror("unknown unit %s", cp);
+ free($1);
+ YYERROR;
+ }
+ }
+ free($1);
+ $$.bw_absolute = (u_int64_t)bps;
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 >= LLONG_MAX) {
+ yyerror("bandwidth number too big");
+ YYERROR;
+ }
+ $$.bw_percent = 0;
+ $$.bw_absolute = $1;
+ }
+ ;
+
+scheduler : CBQ {
+ $$.qtype = ALTQT_CBQ;
+ $$.data.cbq_opts.flags = 0;
+ }
+ | CBQ '(' cbqflags_list ')' {
+ $$.qtype = ALTQT_CBQ;
+ $$.data.cbq_opts.flags = $3;
+ }
+ | PRIQ {
+ $$.qtype = ALTQT_PRIQ;
+ $$.data.priq_opts.flags = 0;
+ }
+ | PRIQ '(' priqflags_list ')' {
+ $$.qtype = ALTQT_PRIQ;
+ $$.data.priq_opts.flags = $3;
+ }
+ | HFSC {
+ $$.qtype = ALTQT_HFSC;
+ bzero(&$$.data.hfsc_opts,
+ sizeof(struct node_hfsc_opts));
+ }
+ | HFSC '(' hfsc_opts ')' {
+ $$.qtype = ALTQT_HFSC;
+ $$.data.hfsc_opts = $3;
+ }
+ | FAIRQ {
+ $$.qtype = ALTQT_FAIRQ;
+ bzero(&$$.data.fairq_opts,
+ sizeof(struct node_fairq_opts));
+ }
+ | FAIRQ '(' fairq_opts ')' {
+ $$.qtype = ALTQT_FAIRQ;
+ $$.data.fairq_opts = $3;
+ }
+ | CODEL {
+ $$.qtype = ALTQT_CODEL;
+ bzero(&$$.data.codel_opts,
+ sizeof(struct codel_opts));
+ }
+ | CODEL '(' codel_opts ')' {
+ $$.qtype = ALTQT_CODEL;
+ $$.data.codel_opts = $3;
+ }
+ ;
+
+cbqflags_list : cbqflags_item { $$ |= $1; }
+ | cbqflags_list comma cbqflags_item { $$ |= $3; }
+ ;
+
+cbqflags_item : STRING {
+ if (!strcmp($1, "default"))
+ $$ = CBQCLF_DEFCLASS;
+ else if (!strcmp($1, "borrow"))
+ $$ = CBQCLF_BORROW;
+ else if (!strcmp($1, "red"))
+ $$ = CBQCLF_RED;
+ else if (!strcmp($1, "ecn"))
+ $$ = CBQCLF_RED|CBQCLF_ECN;
+ else if (!strcmp($1, "rio"))
+ $$ = CBQCLF_RIO;
+ else if (!strcmp($1, "codel"))
+ $$ = CBQCLF_CODEL;
+ else {
+ yyerror("unknown cbq flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+priqflags_list : priqflags_item { $$ |= $1; }
+ | priqflags_list comma priqflags_item { $$ |= $3; }
+ ;
+
+priqflags_item : STRING {
+ if (!strcmp($1, "default"))
+ $$ = PRCF_DEFAULTCLASS;
+ else if (!strcmp($1, "red"))
+ $$ = PRCF_RED;
+ else if (!strcmp($1, "ecn"))
+ $$ = PRCF_RED|PRCF_ECN;
+ else if (!strcmp($1, "rio"))
+ $$ = PRCF_RIO;
+ else if (!strcmp($1, "codel"))
+ $$ = PRCF_CODEL;
+ else {
+ yyerror("unknown priq flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+hfsc_opts : {
+ bzero(&hfsc_opts,
+ sizeof(struct node_hfsc_opts));
+ }
+ hfscopts_list {
+ $$ = hfsc_opts;
+ }
+ ;
+
+hfscopts_list : hfscopts_item
+ | hfscopts_list comma hfscopts_item
+ ;
+
+hfscopts_item : LINKSHARE bandwidth {
+ if (hfsc_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ hfsc_opts.linkshare.m2 = $2;
+ hfsc_opts.linkshare.used = 1;
+ }
+ | LINKSHARE '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ hfsc_opts.linkshare.m1 = $3;
+ hfsc_opts.linkshare.d = $5;
+ hfsc_opts.linkshare.m2 = $7;
+ hfsc_opts.linkshare.used = 1;
+ }
+ | REALTIME bandwidth {
+ if (hfsc_opts.realtime.used) {
+ yyerror("realtime already specified");
+ YYERROR;
+ }
+ hfsc_opts.realtime.m2 = $2;
+ hfsc_opts.realtime.used = 1;
+ }
+ | REALTIME '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.realtime.used) {
+ yyerror("realtime already specified");
+ YYERROR;
+ }
+ hfsc_opts.realtime.m1 = $3;
+ hfsc_opts.realtime.d = $5;
+ hfsc_opts.realtime.m2 = $7;
+ hfsc_opts.realtime.used = 1;
+ }
+ | UPPERLIMIT bandwidth {
+ if (hfsc_opts.upperlimit.used) {
+ yyerror("upperlimit already specified");
+ YYERROR;
+ }
+ hfsc_opts.upperlimit.m2 = $2;
+ hfsc_opts.upperlimit.used = 1;
+ }
+ | UPPERLIMIT '(' bandwidth comma NUMBER comma bandwidth ')'
+ {
+ if ($5 < 0 || $5 > INT_MAX) {
+ yyerror("timing in curve out of range");
+ YYERROR;
+ }
+ if (hfsc_opts.upperlimit.used) {
+ yyerror("upperlimit already specified");
+ YYERROR;
+ }
+ hfsc_opts.upperlimit.m1 = $3;
+ hfsc_opts.upperlimit.d = $5;
+ hfsc_opts.upperlimit.m2 = $7;
+ hfsc_opts.upperlimit.used = 1;
+ }
+ | STRING {
+ if (!strcmp($1, "default"))
+ hfsc_opts.flags |= HFCF_DEFAULTCLASS;
+ else if (!strcmp($1, "red"))
+ hfsc_opts.flags |= HFCF_RED;
+ else if (!strcmp($1, "ecn"))
+ hfsc_opts.flags |= HFCF_RED|HFCF_ECN;
+ else if (!strcmp($1, "rio"))
+ hfsc_opts.flags |= HFCF_RIO;
+ else if (!strcmp($1, "codel"))
+ hfsc_opts.flags |= HFCF_CODEL;
+ else {
+ yyerror("unknown hfsc flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+fairq_opts : {
+ bzero(&fairq_opts,
+ sizeof(struct node_fairq_opts));
+ }
+ fairqopts_list {
+ $$ = fairq_opts;
+ }
+ ;
+
+fairqopts_list : fairqopts_item
+ | fairqopts_list comma fairqopts_item
+ ;
+
+fairqopts_item : LINKSHARE bandwidth {
+ if (fairq_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ fairq_opts.linkshare.m2 = $2;
+ fairq_opts.linkshare.used = 1;
+ }
+ | LINKSHARE '(' bandwidth number bandwidth ')' {
+ if (fairq_opts.linkshare.used) {
+ yyerror("linkshare already specified");
+ YYERROR;
+ }
+ fairq_opts.linkshare.m1 = $3;
+ fairq_opts.linkshare.d = $4;
+ fairq_opts.linkshare.m2 = $5;
+ fairq_opts.linkshare.used = 1;
+ }
+ | HOGS bandwidth {
+ fairq_opts.hogs_bw = $2;
+ }
+ | BUCKETS number {
+ fairq_opts.nbuckets = $2;
+ }
+ | STRING {
+ if (!strcmp($1, "default"))
+ fairq_opts.flags |= FARF_DEFAULTCLASS;
+ else if (!strcmp($1, "red"))
+ fairq_opts.flags |= FARF_RED;
+ else if (!strcmp($1, "ecn"))
+ fairq_opts.flags |= FARF_RED|FARF_ECN;
+ else if (!strcmp($1, "rio"))
+ fairq_opts.flags |= FARF_RIO;
+ else if (!strcmp($1, "codel"))
+ fairq_opts.flags |= FARF_CODEL;
+ else {
+ yyerror("unknown fairq flag \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+codel_opts : {
+ bzero(&codel_opts,
+ sizeof(struct codel_opts));
+ }
+ codelopts_list {
+ $$ = codel_opts;
+ }
+ ;
+
+codelopts_list : codelopts_item
+ | codelopts_list comma codelopts_item
+ ;
+
+codelopts_item : INTERVAL number {
+ if (codel_opts.interval) {
+ yyerror("interval already specified");
+ YYERROR;
+ }
+ codel_opts.interval = $2;
+ }
+ | TARGET number {
+ if (codel_opts.target) {
+ yyerror("target already specified");
+ YYERROR;
+ }
+ codel_opts.target = $2;
+ }
+ | STRING {
+ if (!strcmp($1, "ecn"))
+ codel_opts.ecn = 1;
+ else {
+ yyerror("unknown codel option \"%s\"", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+qassign : /* empty */ { $$ = NULL; }
+ | qassign_item { $$ = $1; }
+ | '{' optnl qassign_list '}' { $$ = $3; }
+ ;
+
+qassign_list : qassign_item optnl { $$ = $1; }
+ | qassign_list comma qassign_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+qassign_item : STRING {
+ $$ = calloc(1, sizeof(struct node_queue));
+ if ($$ == NULL)
+ err(1, "qassign_item: calloc");
+ if (strlcpy($$->queue, $1, sizeof($$->queue)) >=
+ sizeof($$->queue)) {
+ yyerror("queue name '%s' too long (max "
+ "%d chars)", $1, sizeof($$->queue)-1);
+ free($1);
+ free($$);
+ YYERROR;
+ }
+ free($1);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+pfrule : action dir logquick interface route af proto fromto
+ filter_opts
+ {
+ struct pfctl_rule r;
+ struct node_state_opt *o;
+ struct node_proto *proto;
+ int srctrack = 0;
+ int statelock = 0;
+ int adaptive = 0;
+ int defaults = 0;
+
+ if (check_rulestate(PFCTL_STATE_FILTER))
+ YYERROR;
+
+ pfctl_init_rule(&r);
+
+ r.action = $1.b1;
+ switch ($1.b2) {
+ case PFRULE_RETURNRST:
+ r.rule_flag |= PFRULE_RETURNRST;
+ r.return_ttl = $1.w;
+ break;
+ case PFRULE_RETURNICMP:
+ r.rule_flag |= PFRULE_RETURNICMP;
+ r.return_icmp = $1.w;
+ r.return_icmp6 = $1.w2;
+ break;
+ case PFRULE_RETURN:
+ r.rule_flag |= PFRULE_RETURN;
+ r.return_icmp = $1.w;
+ r.return_icmp6 = $1.w2;
+ break;
+ }
+ r.direction = $2;
+ r.log = $3.log;
+ r.logif = $3.logif;
+ r.quick = $3.quick;
+ r.af = $6;
+
+ if (filteropts_to_rule(&r, &$9))
+ YYERROR;
+
+ if ($9.flags.b1 || $9.flags.b2 || $8.src_os) {
+ for (proto = $7; proto != NULL &&
+ proto->proto != IPPROTO_TCP;
+ proto = proto->next)
+ ; /* nothing */
+ if (proto == NULL && $7 != NULL) {
+ if ($9.flags.b1 || $9.flags.b2)
+ yyerror(
+ "flags only apply to tcp");
+ if ($8.src_os)
+ yyerror(
+ "OS fingerprinting only "
+ "apply to tcp");
+ YYERROR;
+ }
+ }
+
+ o = $9.keep.options;
+
+ /* 'keep state' by default on pass rules. */
+ if (!r.keep_state && !r.action &&
+ !($9.marker & FOM_KEEP)) {
+ r.keep_state = PF_STATE_NORMAL;
+ o = keep_state_defaults;
+ defaults = 1;
+ }
+
+ while (o) {
+ struct node_state_opt *p = o;
+
+ switch (o->type) {
+ case PF_STATE_OPT_MAX:
+ if (r.max_states) {
+ yyerror("state option 'max' "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.max_states = o->data.max_states;
+ break;
+ case PF_STATE_OPT_NOSYNC:
+ if (r.rule_flag & PFRULE_NOSYNC) {
+ yyerror("state option 'sync' "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_NOSYNC;
+ break;
+ case PF_STATE_OPT_SRCTRACK:
+ if (srctrack) {
+ yyerror("state option "
+ "'source-track' "
+ "multiple definitions");
+ YYERROR;
+ }
+ srctrack = o->data.src_track;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_STATES:
+ if (r.max_src_states) {
+ yyerror("state option "
+ "'max-src-states' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_states == 0) {
+ yyerror("'max-src-states' must "
+ "be > 0");
+ YYERROR;
+ }
+ r.max_src_states =
+ o->data.max_src_states;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ break;
+ case PF_STATE_OPT_OVERLOAD:
+ if (r.overload_tblname[0]) {
+ yyerror("multiple 'overload' "
+ "table definitions");
+ YYERROR;
+ }
+ if (strlcpy(r.overload_tblname,
+ o->data.overload.tblname,
+ PF_TABLE_NAME_SIZE) >=
+ PF_TABLE_NAME_SIZE) {
+ yyerror("state option: "
+ "strlcpy");
+ YYERROR;
+ }
+ r.flush = o->data.overload.flush;
+ break;
+ case PF_STATE_OPT_MAX_SRC_CONN:
+ if (r.max_src_conn) {
+ yyerror("state option "
+ "'max-src-conn' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_conn == 0) {
+ yyerror("'max-src-conn' "
+ "must be > 0");
+ YYERROR;
+ }
+ r.max_src_conn =
+ o->data.max_src_conn;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_CONN_RATE:
+ if (r.max_src_conn_rate.limit) {
+ yyerror("state option "
+ "'max-src-conn-rate' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (!o->data.max_src_conn_rate.limit ||
+ !o->data.max_src_conn_rate.seconds) {
+ yyerror("'max-src-conn-rate' "
+ "values must be > 0");
+ YYERROR;
+ }
+ if (o->data.max_src_conn_rate.limit >
+ PF_THRESHOLD_MAX) {
+ yyerror("'max-src-conn-rate' "
+ "maximum rate must be < %u",
+ PF_THRESHOLD_MAX);
+ YYERROR;
+ }
+ r.max_src_conn_rate.limit =
+ o->data.max_src_conn_rate.limit;
+ r.max_src_conn_rate.seconds =
+ o->data.max_src_conn_rate.seconds;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_MAX_SRC_NODES:
+ if (r.max_src_nodes) {
+ yyerror("state option "
+ "'max-src-nodes' "
+ "multiple definitions");
+ YYERROR;
+ }
+ if (o->data.max_src_nodes == 0) {
+ yyerror("'max-src-nodes' must "
+ "be > 0");
+ YYERROR;
+ }
+ r.max_src_nodes =
+ o->data.max_src_nodes;
+ r.rule_flag |= PFRULE_SRCTRACK |
+ PFRULE_RULESRCTRACK;
+ break;
+ case PF_STATE_OPT_STATELOCK:
+ if (statelock) {
+ yyerror("state locking option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ statelock = 1;
+ r.rule_flag |= o->data.statelock;
+ break;
+ case PF_STATE_OPT_SLOPPY:
+ if (r.rule_flag & PFRULE_STATESLOPPY) {
+ yyerror("state sloppy option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_STATESLOPPY;
+ break;
+ case PF_STATE_OPT_PFLOW:
+ if (r.rule_flag & PFRULE_PFLOW) {
+ yyerror("state pflow option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_PFLOW;
+ break;
+ case PF_STATE_OPT_ALLOW_RELATED:
+ if (r.rule_flag & PFRULE_ALLOW_RELATED) {
+ yyerror("state allow-related option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_ALLOW_RELATED;
+ break;
+ case PF_STATE_OPT_TIMEOUT:
+ if (o->data.timeout.number ==
+ PFTM_ADAPTIVE_START ||
+ o->data.timeout.number ==
+ PFTM_ADAPTIVE_END)
+ adaptive = 1;
+ if (r.timeout[o->data.timeout.number]) {
+ yyerror("state timeout %s "
+ "multiple definitions",
+ pf_timeouts[o->data.
+ timeout.number].name);
+ YYERROR;
+ }
+ r.timeout[o->data.timeout.number] =
+ o->data.timeout.seconds;
+ }
+ o = o->next;
+ if (!defaults)
+ free(p);
+ }
+
+ /* 'flags S/SA' by default on stateful rules */
+ if (!r.action && !r.flags && !r.flagset &&
+ !$9.fragment && !($9.marker & FOM_FLAGS) &&
+ r.keep_state) {
+ r.flags = parse_flags("S");
+ r.flagset = parse_flags("SA");
+ }
+ if (!adaptive && r.max_states) {
+ r.timeout[PFTM_ADAPTIVE_START] =
+ (r.max_states / 10) * 6;
+ r.timeout[PFTM_ADAPTIVE_END] =
+ (r.max_states / 10) * 12;
+ }
+ if (r.rule_flag & PFRULE_SRCTRACK) {
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_nodes) {
+ yyerror("'max-src-nodes' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_conn) {
+ yyerror("'max-src-conn' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (srctrack == PF_SRCTRACK_GLOBAL &&
+ r.max_src_conn_rate.seconds) {
+ yyerror("'max-src-conn-rate' is "
+ "incompatible with "
+ "'source-track global'");
+ YYERROR;
+ }
+ if (r.timeout[PFTM_SRC_NODE] <
+ r.max_src_conn_rate.seconds)
+ r.timeout[PFTM_SRC_NODE] =
+ r.max_src_conn_rate.seconds;
+ r.rule_flag |= PFRULE_SRCTRACK;
+ if (srctrack == PF_SRCTRACK_RULE)
+ r.rule_flag |= PFRULE_RULESRCTRACK;
+ }
+ if (r.keep_state && !statelock)
+ r.rule_flag |= default_statelock;
+
+ decide_address_family($8.src.host, &r.af);
+ decide_address_family($8.dst.host, &r.af);
+
+ if ($5.rt) {
+ if (!r.direction) {
+ yyerror("direction must be explicit "
+ "with rules that specify routing");
+ YYERROR;
+ }
+ r.rt = $5.rt;
+
+ if (!($5.redirspec->pool_opts.opts & PF_POOL_IPV6NH)) {
+ decide_address_family($5.redirspec->host, &r.af);
+ if (!(r.rule_flag & PFRULE_AFTO))
+ remove_invalid_hosts(&($5.redirspec->host), &r.af);
+ if ($5.redirspec->host == NULL) {
+ yyerror("no routing address with "
+ "matching address family found.");
+ YYERROR;
+ }
+ }
+ }
+#ifdef __FreeBSD__
+ r.divert.port = $9.divert.port;
+#else
+ if ((r.divert.port = $9.divert.port)) {
+ if (r.direction == PF_OUT) {
+ if ($9.divert.addr) {
+ yyerror("address specified "
+ "for outgoing divert");
+ YYERROR;
+ }
+ bzero(&r.divert.addr,
+ sizeof(r.divert.addr));
+ } else {
+ if (!$9.divert.addr) {
+ yyerror("no address specified "
+ "for incoming divert");
+ YYERROR;
+ }
+ if ($9.divert.addr->af != r.af) {
+ yyerror("address family "
+ "mismatch for divert");
+ YYERROR;
+ }
+ r.divert.addr =
+ $9.divert.addr->addr.v.a.addr;
+ }
+ }
+#endif
+
+ if ($9.dnpipe || $9.dnrpipe) {
+ r.dnpipe = $9.dnpipe;
+ r.dnrpipe = $9.dnrpipe;
+ if ($9.free_flags & PFRULE_DN_IS_PIPE)
+ r.free_flags |= PFRULE_DN_IS_PIPE;
+ else
+ r.free_flags |= PFRULE_DN_IS_QUEUE;
+ }
+
+ if ($9.marker & FOM_AFTO) {
+ r.naf = $9.nat->af;
+ } else {
+ if ($9.nat) {
+ if (!r.af && ! $9.nat->host->ifindex)
+ r.af = $9.nat->host->af;
+ remove_invalid_hosts(&($9.nat->host), &r.af);
+ if (invalid_redirect($9.nat->host, r.af))
+ YYERROR;
+ if ($9.nat->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9.nat->host = gen_dynnode($9.nat->host, r.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9.nat->host, r.af))
+ YYERROR;
+ }
+ if ($9.rdr) {
+ if (!r.af && ! $9.rdr->host->ifindex)
+ r.af = $9.rdr->host->af;
+ remove_invalid_hosts(&($9.rdr->host), &r.af);
+ if (invalid_redirect($9.rdr->host, r.af))
+ YYERROR;
+ if ($9.rdr->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9.rdr->host = gen_dynnode($9.rdr->host, r.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9.rdr->host, r.af))
+ YYERROR;
+ }
+ }
+
+ expand_rule(&r, false, $4, $9.nat, $9.rdr, $5.redirspec,
+ $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host,
+ $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec);
+ }
+ ;
+
+filter_opts : {
+ bzero(&filter_opts, sizeof filter_opts);
+ filter_opts.rtableid = -1;
+ }
+ filter_opts_l
+ { $$ = filter_opts; }
+ | /* empty */ {
+ bzero(&filter_opts, sizeof filter_opts);
+ filter_opts.rtableid = -1;
+ $$ = filter_opts;
+ }
+ ;
+
+filter_opts_l : filter_opts_l filter_opt
+ | filter_opt
+ ;
+
+filter_opt : USER uids {
+ if (filter_opts.uid)
+ $2->tail->next = filter_opts.uid;
+ filter_opts.uid = $2;
+ }
+ | GROUP gids {
+ if (filter_opts.gid)
+ $2->tail->next = filter_opts.gid;
+ filter_opts.gid = $2;
+ }
+ | flags {
+ if (filter_opts.marker & FOM_FLAGS) {
+ yyerror("flags cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_FLAGS;
+ filter_opts.flags.b1 |= $1.b1;
+ filter_opts.flags.b2 |= $1.b2;
+ filter_opts.flags.w |= $1.w;
+ filter_opts.flags.w2 |= $1.w2;
+ }
+ | icmpspec {
+ if (filter_opts.marker & FOM_ICMP) {
+ yyerror("icmp-type cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_ICMP;
+ filter_opts.icmpspec = $1;
+ }
+ | PRIO NUMBER {
+ if (filter_opts.marker & FOM_PRIO) {
+ yyerror("prio cannot be redefined");
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > PF_PRIO_MAX) {
+ yyerror("prio must be 0 - %u", PF_PRIO_MAX);
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_PRIO;
+ filter_opts.prio = $2;
+ }
+ | TOS tos {
+ if (filter_opts.marker & FOM_TOS) {
+ yyerror("tos cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_TOS;
+ filter_opts.tos = $2;
+ }
+ | keep {
+ if (filter_opts.marker & FOM_KEEP) {
+ yyerror("modulate or keep cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_KEEP;
+ filter_opts.keep.action = $1.action;
+ filter_opts.keep.options = $1.options;
+ }
+ | RIDENTIFIER number {
+ filter_opts.ridentifier = $2;
+ }
+ | FRAGMENT {
+ filter_opts.fragment = 1;
+ }
+ | ALLOWOPTS {
+ filter_opts.allowopts = 1;
+ }
+ | label {
+ if (filter_opts.labelcount >= PF_RULE_MAX_LABEL_COUNT) {
+ yyerror("label can only be used %d times", PF_RULE_MAX_LABEL_COUNT);
+ YYERROR;
+ }
+ filter_opts.label[filter_opts.labelcount++] = $1;
+ }
+ | qname {
+ if (filter_opts.queues.qname) {
+ yyerror("queue cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.queues = $1;
+ }
+ | DNPIPE number {
+ filter_opts.dnpipe = $2;
+ filter_opts.free_flags |= PFRULE_DN_IS_PIPE;
+ }
+ | DNPIPE '(' number ')' {
+ filter_opts.dnpipe = $3;
+ filter_opts.free_flags |= PFRULE_DN_IS_PIPE;
+ }
+ | DNPIPE '(' number comma number ')' {
+ filter_opts.dnrpipe = $5;
+ filter_opts.dnpipe = $3;
+ filter_opts.free_flags |= PFRULE_DN_IS_PIPE;
+ }
+ | DNQUEUE number {
+ filter_opts.dnpipe = $2;
+ filter_opts.free_flags |= PFRULE_DN_IS_QUEUE;
+ }
+ | DNQUEUE '(' number comma number ')' {
+ filter_opts.dnrpipe = $5;
+ filter_opts.dnpipe = $3;
+ filter_opts.free_flags |= PFRULE_DN_IS_QUEUE;
+ }
+ | DNQUEUE '(' number ')' {
+ filter_opts.dnpipe = $3;
+ filter_opts.free_flags |= PFRULE_DN_IS_QUEUE;
+ }
+ | TAG string {
+ filter_opts.tag = $2;
+ }
+ | not TAGGED string {
+ filter_opts.match_tag = $3;
+ filter_opts.match_tag_not = $1;
+ }
+ | not RECEIVEDON if_item {
+ if (filter_opts.rcv) {
+ yyerror("cannot respecify received-on");
+ YYERROR;
+ }
+ filter_opts.rcv = $3;
+ filter_opts.rcv->not = $1;
+ }
+ | PROBABILITY probability {
+ double p;
+
+ p = floor($2 * UINT_MAX + 0.5);
+ if (p < 0.0 || p > UINT_MAX) {
+ yyerror("invalid probability: %lf", p);
+ YYERROR;
+ }
+ filter_opts.prob = (u_int32_t)p;
+ if (filter_opts.prob == 0)
+ filter_opts.prob = 1;
+ }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ filter_opts.rtableid = $2;
+ }
+ | DIVERTTO portplain {
+#ifdef __FreeBSD__
+ filter_opts.divert.port = $2.a;
+ if (!filter_opts.divert.port) {
+ yyerror("invalid divert port: %u", ntohs($2.a));
+ YYERROR;
+ }
+#endif
+ }
+ | DIVERTTO STRING PORT portplain {
+#ifndef __FreeBSD__
+ if ((filter_opts.divert.addr = host($2, pf->opts)) == NULL) {
+ yyerror("could not parse divert address: %s",
+ $2);
+ free($2);
+ YYERROR;
+ }
+#else
+ if ($2)
+#endif
+ free($2);
+ filter_opts.divert.port = $4.a;
+ if (!filter_opts.divert.port) {
+ yyerror("invalid divert port: %u", ntohs($4.a));
+ YYERROR;
+ }
+ }
+ | DIVERTREPLY {
+#ifdef __FreeBSD__
+ yyerror("divert-reply has no meaning in FreeBSD pf(4)");
+ YYERROR;
+#else
+ filter_opts.divert.port = 1; /* some random value */
+#endif
+ }
+ | SCRUB '(' scrub_opts ')' {
+ filter_opts.nodf = $3.nodf;
+ filter_opts.minttl = $3.minttl;
+ if ($3.marker & FOM_SETTOS) {
+ /* Old style rules are "scrub set-tos 0x42"
+ * New style are "set tos 0x42 scrub (...)"
+ * What is in "scrub(...)"" is unfortunately the
+ * original scrub syntax so it would overwrite
+ * "set tos" of a pass/match rule.
+ */
+ filter_opts.settos = $3.settos;
+ }
+ filter_opts.randomid = $3.randomid;
+ filter_opts.max_mss = $3.maxmss;
+ if ($3.reassemble_tcp)
+ filter_opts.marker |= FOM_SCRUB_TCP;
+ filter_opts.marker |= $3.marker;
+ }
+ | NATTO port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify nat-to/binat-to");
+ YYERROR;
+ }
+ filter_opts.nat = $2;
+ }
+ | RDRTO port_redirspec {
+ if (filter_opts.rdr) {
+ yyerror("cannot respecify rdr-to");
+ YYERROR;
+ }
+ filter_opts.rdr = $2;
+ }
+ | BINATTO port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify nat-to/binat-to");
+ YYERROR;
+ }
+ filter_opts.nat = $2;
+ filter_opts.nat->binat = 1;
+ filter_opts.nat->pool_opts.staticport = 1;
+ }
+ | AFTO af FROM port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify af-to");
+ YYERROR;
+ }
+ if ($2 == 0) {
+ yyerror("no address family specified");
+ YYERROR;
+ }
+
+ filter_opts.nat = $4;
+ filter_opts.nat->af = $2;
+ remove_invalid_hosts(&($4->host), &(filter_opts.nat->af));
+ if ($4->host == NULL) {
+ yyerror("af-to addresses must be in the "
+ "target address family");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_AFTO;
+ }
+ | AFTO af FROM port_redirspec TO port_redirspec {
+ if (filter_opts.nat) {
+ yyerror("cannot respecify af-to");
+ YYERROR;
+ }
+ if ($2 == 0) {
+ yyerror("no address family specified");
+ YYERROR;
+ }
+ filter_opts.nat = $4;
+ filter_opts.nat->af = $2;
+ filter_opts.rdr = $6;
+ filter_opts.rdr->af = $2;
+ remove_invalid_hosts(&($4->host), &(filter_opts.nat->af));
+ remove_invalid_hosts(&($6->host), &(filter_opts.rdr->af));
+ if ($4->host == NULL || $6->host == NULL) {
+ yyerror("af-to addresses must be in the "
+ "target address family");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_AFTO;
+ }
+ | MAXPKTRATE NUMBER '/' NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX ||
+ $4 < 0 || $4 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (filter_opts.pktrate.limit) {
+ yyerror("cannot respecify max-pkt-rate");
+ YYERROR;
+ }
+ filter_opts.pktrate.limit = $2;
+ filter_opts.pktrate.seconds = $4;
+ }
+ | MAXPKTSIZE NUMBER {
+ if ($2 < 0 || $2 > UINT16_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ filter_opts.max_pkt_size = $2;
+ }
+ | ONCE {
+ filter_opts.marker |= FOM_ONCE;
+ }
+ | filter_sets
+ ;
+
+filter_sets : SET '(' filter_sets_l ')' { $$ = filter_opts; }
+ | SET filter_set { $$ = filter_opts; }
+ ;
+
+filter_sets_l : filter_sets_l comma filter_set
+ | filter_set
+ ;
+
+filter_set : prio {
+ if (filter_opts.marker & FOM_SETPRIO) {
+ yyerror("prio cannot be redefined");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_SETPRIO;
+ filter_opts.set_prio[0] = $1.b1;
+ filter_opts.set_prio[1] = $1.b2;
+ }
+ | TOS tos {
+ if (filter_opts.marker & FOM_SETTOS) {
+ yyerror("tos cannot be respecified");
+ YYERROR;
+ }
+ filter_opts.marker |= FOM_SETTOS;
+ filter_opts.settos = $2;
+ }
+prio : PRIO NUMBER {
+ if ($2 < 0 || $2 > PF_PRIO_MAX) {
+ yyerror("prio must be 0 - %u", PF_PRIO_MAX);
+ YYERROR;
+ }
+ $$.b1 = $$.b2 = $2;
+ }
+ | PRIO '(' NUMBER comma NUMBER ')' {
+ if ($3 < 0 || $3 > PF_PRIO_MAX ||
+ $5 < 0 || $5 > PF_PRIO_MAX) {
+ yyerror("prio must be 0 - %u", PF_PRIO_MAX);
+ YYERROR;
+ }
+ $$.b1 = $3;
+ $$.b2 = $5;
+ }
+ ;
+
+probability : STRING {
+ char *e;
+ double p = strtod($1, &e);
+
+ if (*e == '%') {
+ p *= 0.01;
+ e++;
+ }
+ if (*e) {
+ yyerror("invalid probability: %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$ = p;
+ }
+ | NUMBER {
+ $$ = (double)$1;
+ }
+ ;
+
+
+action : PASS {
+ $$.b1 = PF_PASS;
+ $$.b2 = failpolicy;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | MATCH { $$.b1 = PF_MATCH; $$.b2 = $$.w = 0; }
+ | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; }
+ ;
+
+blockspec : /* empty */ {
+ $$.b2 = blockpolicy;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | DROP {
+ $$.b2 = PFRULE_DROP;
+ $$.w = 0;
+ $$.w2 = 0;
+ }
+ | RETURNRST {
+ $$.b2 = PFRULE_RETURNRST;
+ $$.w = 0;
+ $$.w2 = 0;
+ }
+ | RETURNRST '(' TTL NUMBER ')' {
+ if ($4 < 0 || $4 > 255) {
+ yyerror("illegal ttl value %d", $4);
+ YYERROR;
+ }
+ $$.b2 = PFRULE_RETURNRST;
+ $$.w = $4;
+ $$.w2 = 0;
+ }
+ | RETURNICMP {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | RETURNICMP6 {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ | RETURNICMP '(' reticmpspec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = $3;
+ $$.w2 = returnicmpdefault;
+ }
+ | RETURNICMP6 '(' reticmp6spec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = returnicmpdefault;
+ $$.w2 = $3;
+ }
+ | RETURNICMP '(' reticmpspec comma reticmp6spec ')' {
+ $$.b2 = PFRULE_RETURNICMP;
+ $$.w = $3;
+ $$.w2 = $5;
+ }
+ | RETURN {
+ $$.b2 = PFRULE_RETURN;
+ $$.w = returnicmpdefault;
+ $$.w2 = returnicmp6default;
+ }
+ ;
+
+reticmpspec : STRING {
+ if (!($$ = parseicmpspec($1, AF_INET))) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ u_int8_t icmptype;
+
+ if ($1 < 0 || $1 > 255) {
+ yyerror("invalid icmp code %lu", $1);
+ YYERROR;
+ }
+ icmptype = returnicmpdefault >> 8;
+ $$ = (icmptype << 8 | $1);
+ }
+ ;
+
+reticmp6spec : STRING {
+ if (!($$ = parseicmpspec($1, AF_INET6))) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ u_int8_t icmptype;
+
+ if ($1 < 0 || $1 > 255) {
+ yyerror("invalid icmp code %lu", $1);
+ YYERROR;
+ }
+ icmptype = returnicmp6default >> 8;
+ $$ = (icmptype << 8 | $1);
+ }
+ ;
+
+dir : /* empty */ { $$ = PF_INOUT; }
+ | IN { $$ = PF_IN; }
+ | OUT { $$ = PF_OUT; }
+ ;
+
+quick : /* empty */ { $$.quick = 0; }
+ | QUICK { $$.quick = 1; }
+ ;
+
+logquick : /* empty */ { $$.log = 0; $$.quick = 0; $$.logif = 0; }
+ | log { $$ = $1; $$.quick = 0; }
+ | QUICK { $$.quick = 1; $$.log = 0; $$.logif = 0; }
+ | log QUICK { $$ = $1; $$.quick = 1; }
+ | QUICK log { $$ = $2; $$.quick = 1; }
+ ;
+
+log : LOG { $$.log = PF_LOG; $$.logif = 0; }
+ | LOG '(' logopts ')' {
+ $$.log = PF_LOG | $3.log;
+ $$.logif = $3.logif;
+ }
+ ;
+
+logopts : logopt { $$ = $1; }
+ | logopts comma logopt {
+ $$.log = $1.log | $3.log;
+ $$.logif = $3.logif;
+ if ($$.logif == 0)
+ $$.logif = $1.logif;
+ }
+ ;
+
+logopt : ALL { $$.log = PF_LOG_ALL; $$.logif = 0; }
+ | MATCHES { $$.log = PF_LOG_MATCHES; $$.logif = 0; }
+ | USER { $$.log = PF_LOG_USER; $$.logif = 0; }
+ | TO string {
+ const char *errstr;
+ u_int i;
+
+ $$.log = 0;
+ if (strncmp($2, "pflog", 5)) {
+ yyerror("%s: should be a pflog interface", $2);
+ free($2);
+ YYERROR;
+ }
+ i = strtonum($2 + 5, 0, 255, &errstr);
+ if (errstr) {
+ yyerror("%s: %s", $2, errstr);
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ $$.logif = i;
+ }
+ ;
+
+interface : /* empty */ { $$ = NULL; }
+ | ON if_item_not { $$ = $2; }
+ | ON '{' optnl if_list '}' { $$ = $4; }
+ ;
+
+if_list : if_item_not optnl { $$ = $1; }
+ | if_list comma if_item_not optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+if_item_not : not if_item { $$ = $2; $$->not = $1; }
+ ;
+
+if_item : STRING {
+ struct node_host *n;
+
+ $$ = calloc(1, sizeof(struct node_if));
+ if ($$ == NULL)
+ err(1, "if_item: calloc");
+ if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >=
+ sizeof($$->ifname)) {
+ free($1);
+ free($$);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+
+ if ((n = ifa_exists($1)) != NULL)
+ $$->ifa_flags = n->ifa_flags;
+
+ free($1);
+ $$->not = 0;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | ANY {
+ $$ = calloc(1, sizeof(struct node_if));
+ if ($$ == NULL)
+ err(1, "if_item: calloc");
+ strlcpy($$->ifname, "any", sizeof($$->ifname));
+ $$->not = 0;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+af : /* empty */ { $$ = 0; }
+ | INET { $$ = AF_INET; }
+ | INET6 { $$ = AF_INET6; }
+ ;
+
+etherproto : /* empty */ { $$ = NULL; }
+ | PROTO etherproto_item { $$ = $2; }
+ | PROTO '{' optnl etherproto_list '}' { $$ = $4; }
+ ;
+
+etherproto_list : etherproto_item optnl { $$ = $1; }
+ | etherproto_list comma etherproto_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+etherproto_item : etherprotoval {
+ u_int16_t pr;
+
+ pr = (u_int16_t)$1;
+ if (pr == 0) {
+ yyerror("proto 0 cannot be used");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_proto));
+ if ($$ == NULL)
+ err(1, "proto_item: calloc");
+ $$->proto = pr;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+etherprotoval : NUMBER {
+ if ($1 < 0 || $1 > 65565) {
+ yyerror("protocol outside range");
+ YYERROR;
+ }
+ }
+ | STRING
+ {
+ if (!strncmp($1, "0x", 2)) {
+ if (sscanf($1, "0x%4x", &$$) != 1) {
+ free($1);
+ yyerror("invalid EtherType hex");
+ YYERROR;
+ }
+ } else {
+ yyerror("Symbolic EtherType not yet supported");
+ }
+ }
+ ;
+
+proto : /* empty */ { $$ = NULL; }
+ | PROTO proto_item { $$ = $2; }
+ | PROTO '{' optnl proto_list '}' { $$ = $4; }
+ ;
+
+proto_list : proto_item optnl { $$ = $1; }
+ | proto_list comma proto_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+proto_item : protoval {
+ u_int8_t pr;
+
+ pr = (u_int8_t)$1;
+ if (pr == 0) {
+ yyerror("proto 0 cannot be used");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_proto));
+ if ($$ == NULL)
+ err(1, "proto_item: calloc");
+ $$->proto = pr;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+protoval : STRING {
+ struct protoent *p;
+
+ p = getprotobyname($1);
+ if (p == NULL) {
+ yyerror("unknown protocol %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->p_proto;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("protocol outside range");
+ YYERROR;
+ }
+ }
+ ;
+
+l3fromto : /* empty */ {
+ bzero(&$$, sizeof($$));
+ }
+ | L3 fromto {
+ if ($2.src.host != NULL &&
+ $2.src.host->addr.type != PF_ADDR_ADDRMASK &&
+ $2.src.host->addr.type != PF_ADDR_TABLE) {
+ yyerror("from must be an address or table");
+ YYERROR;
+ }
+ if ($2.dst.host != NULL &&
+ $2.dst.host->addr.type != PF_ADDR_ADDRMASK &&
+ $2.dst.host->addr.type != PF_ADDR_TABLE) {
+ yyerror("to must be an address or table");
+ YYERROR;
+ }
+ $$ = $2;
+ }
+ ;
+etherfromto : ALL {
+ $$.src = NULL;
+ $$.dst = NULL;
+ }
+ | etherfrom etherto {
+ $$.src = $1.mac;
+ $$.dst = $2.mac;
+ }
+ ;
+
+etherfrom : /* emtpy */ {
+ bzero(&$$, sizeof($$));
+ }
+ | FROM macspec {
+ $$.mac = $2;
+ }
+ ;
+
+etherto : /* empty */ {
+ bzero(&$$, sizeof($$));
+ }
+ | TO macspec {
+ $$.mac = $2;
+ }
+ ;
+
+mac : string '/' NUMBER {
+ $$ = node_mac_from_string_masklen($1, $3);
+ free($1);
+ if ($$ == NULL)
+ YYERROR;
+ }
+ | string {
+ if (strchr($1, '&')) {
+ /* mac&mask */
+ char *mac = strtok($1, "&");
+ char *mask = strtok(NULL, "&");
+ $$ = node_mac_from_string_mask(mac, mask);
+ } else {
+ $$ = node_mac_from_string($1);
+ }
+ free($1);
+ if ($$ == NULL)
+ YYERROR;
+
+ }
+xmac : not mac {
+ struct node_mac *n;
+
+ for (n = $2; n != NULL; n = n->next)
+ n->neg = $1;
+ $$ = $2;
+ }
+ ;
+macspec : xmac {
+ $$ = $1;
+ }
+ | '{' optnl mac_list '}'
+ {
+ $$ = $3;
+ }
+ ;
+mac_list : xmac optnl {
+ $$ = $1;
+ }
+ | mac_list comma xmac {
+ if ($3 == NULL)
+ $$ = $1;
+ else if ($1 == NULL)
+ $$ = $3;
+ else {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ }
+
+fromto : ALL {
+ $$.src.host = NULL;
+ $$.src.port = NULL;
+ $$.dst.host = NULL;
+ $$.dst.port = NULL;
+ $$.src_os = NULL;
+ }
+ | from os to {
+ $$.src = $1;
+ $$.src_os = $2;
+ $$.dst = $3;
+ }
+ ;
+
+os : /* empty */ { $$ = NULL; }
+ | OS xos { $$ = $2; }
+ | OS '{' optnl os_list '}' { $$ = $4; }
+ ;
+
+xos : STRING {
+ $$ = calloc(1, sizeof(struct node_os));
+ if ($$ == NULL)
+ err(1, "os: calloc");
+ $$->os = $1;
+ $$->tail = $$;
+ }
+ ;
+
+os_list : xos optnl { $$ = $1; }
+ | os_list comma xos optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+from : /* empty */ {
+ $$.host = NULL;
+ $$.port = NULL;
+ }
+ | FROM ipportspec {
+ $$ = $2;
+ }
+ ;
+
+to : /* empty */ {
+ $$.host = NULL;
+ $$.port = NULL;
+ }
+ | TO ipportspec {
+ if (disallow_urpf_failed($2.host, "\"urpf-failed\" is "
+ "not permitted in a destination address"))
+ YYERROR;
+ $$ = $2;
+ }
+ ;
+
+ipportspec : ipspec {
+ $$.host = $1;
+ $$.port = NULL;
+ }
+ | ipspec PORT portspec {
+ $$.host = $1;
+ $$.port = $3;
+ }
+ | PORT portspec {
+ $$.host = NULL;
+ $$.port = $2;
+ }
+ ;
+
+optnl : '\n' optnl
+ |
+ ;
+
+ipspec : ANY { $$ = NULL; }
+ | xhost { $$ = $1; }
+ | '{' optnl host_list '}' { $$ = $3; }
+ ;
+
+toipspec : TO ipspec { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+host_list : ipspec optnl { $$ = $1; }
+ | host_list comma ipspec optnl {
+ if ($1 == NULL) {
+ freehostlist($3);
+ $$ = $1;
+ } else if ($3 == NULL) {
+ freehostlist($1);
+ $$ = $3;
+ } else {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ }
+ ;
+
+xhost : not host {
+ struct node_host *n;
+
+ for (n = $2; n != NULL; n = n->next)
+ n->not = $1;
+ $$ = $2;
+ }
+ | not NOROUTE {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "xhost: calloc");
+ $$->addr.type = PF_ADDR_NOROUTE;
+ $$->next = NULL;
+ $$->not = $1;
+ $$->tail = $$;
+ }
+ | not URPFFAILED {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "xhost: calloc");
+ $$->addr.type = PF_ADDR_URPFFAILED;
+ $$->next = NULL;
+ $$->not = $1;
+ $$->tail = $$;
+ }
+ ;
+
+host : STRING {
+ if (($$ = host($1, pf->opts)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free($1);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free($1);
+
+ }
+ | STRING '-' STRING {
+ struct node_host *b, *e;
+
+ if ((b = host($1, pf->opts)) == NULL ||
+ (e = host($3, pf->opts)) == NULL) {
+ free($1);
+ free($3);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ if (b->af != e->af ||
+ b->addr.type != PF_ADDR_ADDRMASK ||
+ e->addr.type != PF_ADDR_ADDRMASK ||
+ unmask(&b->addr.v.a.mask) !=
+ (b->af == AF_INET ? 32 : 128) ||
+ unmask(&e->addr.v.a.mask) !=
+ (e->af == AF_INET ? 32 : 128) ||
+ b->next != NULL || b->not ||
+ e->next != NULL || e->not) {
+ free(b);
+ free(e);
+ free($1);
+ free($3);
+ yyerror("invalid address range");
+ YYERROR;
+ }
+ memcpy(&b->addr.v.a.mask, &e->addr.v.a.addr,
+ sizeof(b->addr.v.a.mask));
+ b->addr.type = PF_ADDR_RANGE;
+ $$ = b;
+ free(e);
+ free($1);
+ free($3);
+ }
+ | STRING '/' NUMBER {
+ char *buf;
+
+ if (asprintf(&buf, "%s/%lld", $1, (long long)$3) == -1)
+ err(1, "host: asprintf");
+ free($1);
+ if (($$ = host(buf, pf->opts)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free(buf);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free(buf);
+ }
+ | NUMBER '/' NUMBER {
+ char *buf;
+
+ /* ie. for 10/8 parsing */
+#ifdef __FreeBSD__
+ if (asprintf(&buf, "%lld/%lld", (long long)$1, (long long)$3) == -1)
+#else
+ if (asprintf(&buf, "%lld/%lld", $1, $3) == -1)
+#endif
+ err(1, "host: asprintf");
+ if (($$ = host(buf, pf->opts)) == NULL) {
+ /* error. "any" is handled elsewhere */
+ free(buf);
+ yyerror("could not parse host specification");
+ YYERROR;
+ }
+ free(buf);
+ }
+ | dynaddr
+ | dynaddr '/' NUMBER {
+ struct node_host *n;
+
+ if ($3 < 0 || $3 > 128) {
+ yyerror("bit number too big");
+ YYERROR;
+ }
+ $$ = $1;
+ for (n = $1; n != NULL; n = n->next)
+ set_ipmask(n, $3);
+ }
+ | '<' STRING '>' {
+ if (strlen($2) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name '%s' too long", $2);
+ free($2);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "host: calloc");
+ $$->addr.type = PF_ADDR_TABLE;
+ if (strlcpy($$->addr.v.tblname, $2,
+ sizeof($$->addr.v.tblname)) >=
+ sizeof($$->addr.v.tblname))
+ errx(1, "host: strlcpy");
+ free($2);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+number : NUMBER
+ | STRING {
+ u_long ulval;
+
+ if (atoul($1, &ulval) == -1) {
+ yyerror("%s is not a number", $1);
+ free($1);
+ YYERROR;
+ } else
+ $$ = ulval;
+ free($1);
+ }
+ ;
+
+dynaddr : '(' STRING ')' {
+ int flags = 0;
+ char *p, *op;
+
+ op = $2;
+ if (!isalpha(op[0])) {
+ yyerror("invalid interface name '%s'", op);
+ free(op);
+ YYERROR;
+ }
+ while ((p = strrchr($2, ':')) != NULL) {
+ if (!strcmp(p+1, "network"))
+ flags |= PFI_AFLAG_NETWORK;
+ else if (!strcmp(p+1, "broadcast"))
+ flags |= PFI_AFLAG_BROADCAST;
+ else if (!strcmp(p+1, "peer"))
+ flags |= PFI_AFLAG_PEER;
+ else if (!strcmp(p+1, "0"))
+ flags |= PFI_AFLAG_NOALIAS;
+ else {
+ yyerror("interface %s has bad modifier",
+ $2);
+ free(op);
+ YYERROR;
+ }
+ *p = '\0';
+ }
+ if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) {
+ free(op);
+ yyerror("illegal combination of "
+ "interface modifiers");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "address: calloc");
+ $$->af = 0;
+ set_ipmask($$, 128);
+ $$->addr.type = PF_ADDR_DYNIFTL;
+ $$->addr.iflags = flags;
+ if (strlcpy($$->addr.v.ifname, $2,
+ sizeof($$->addr.v.ifname)) >=
+ sizeof($$->addr.v.ifname)) {
+ free(op);
+ free($$);
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ free(op);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+portspec : port_item { $$ = $1; }
+ | '{' optnl port_list '}' { $$ = $3; }
+ ;
+
+port_list : port_item optnl { $$ = $1; }
+ | port_list comma port_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+port_item : portrange {
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $1.a;
+ $$->port[1] = $1.b;
+ if ($1.t) {
+ $$->op = PF_OP_RRG;
+ if (validate_range($$->op, $$->port[0],
+ $$->port[1])) {
+ yyerror("invalid port range");
+ YYERROR;
+ }
+ } else
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop portrange {
+ if ($2.t) {
+ yyerror("':' cannot be used with an other "
+ "port operator");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $2.a;
+ $$->port[1] = $2.b;
+ $$->op = $1;
+ if (validate_range($$->op, $$->port[0], $$->port[1])) {
+ yyerror("invalid port range");
+ YYERROR;
+ }
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | portrange PORTBINARY portrange {
+ if ($1.t || $3.t) {
+ yyerror("':' cannot be used with an other "
+ "port operator");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_port));
+ if ($$ == NULL)
+ err(1, "port_item: calloc");
+ $$->port[0] = $1.a;
+ $$->port[1] = $3.a;
+ $$->op = $2;
+ if (validate_range($$->op, $$->port[0], $$->port[1])) {
+ yyerror("invalid port range");
+ YYERROR;
+ }
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+portplain : numberstring {
+ if (parseport($1, &$$, 0) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+portrange : numberstring {
+ if (parseport($1, &$$, PPORT_RANGE) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+uids : uid_item { $$ = $1; }
+ | '{' optnl uid_list '}' { $$ = $3; }
+ ;
+
+uid_list : uid_item optnl { $$ = $1; }
+ | uid_list comma uid_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+uid_item : uid {
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $1;
+ $$->uid[1] = $1;
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop uid {
+ if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) {
+ yyerror("user unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $2;
+ $$->uid[1] = $2;
+ $$->op = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | uid PORTBINARY uid {
+ if ($1 == -1 || $3 == -1) {
+ yyerror("user unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_uid));
+ if ($$ == NULL)
+ err(1, "uid_item: calloc");
+ $$->uid[0] = $1;
+ $$->uid[1] = $3;
+ $$->op = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+uid : STRING {
+ if (!strcmp($1, "unknown"))
+ $$ = -1;
+ else {
+ uid_t uid;
+
+ if (uid_from_user($1, &uid) == -1) {
+ yyerror("unknown user %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = uid;
+ }
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 >= UID_MAX) {
+ yyerror("illegal uid value %lu", $1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+gids : gid_item { $$ = $1; }
+ | '{' optnl gid_list '}' { $$ = $3; }
+ ;
+
+gid_list : gid_item optnl { $$ = $1; }
+ | gid_list comma gid_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+gid_item : gid {
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $1;
+ $$->gid[1] = $1;
+ $$->op = PF_OP_EQ;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | unaryop gid {
+ if ($2 == -1 && $1 != PF_OP_EQ && $1 != PF_OP_NE) {
+ yyerror("group unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $2;
+ $$->gid[1] = $2;
+ $$->op = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | gid PORTBINARY gid {
+ if ($1 == -1 || $3 == -1) {
+ yyerror("group unknown requires operator = or "
+ "!=");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_gid));
+ if ($$ == NULL)
+ err(1, "gid_item: calloc");
+ $$->gid[0] = $1;
+ $$->gid[1] = $3;
+ $$->op = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+gid : STRING {
+ if (!strcmp($1, "unknown"))
+ $$ = -1;
+ else {
+ gid_t gid;
+
+ if (gid_from_group($1, &gid) == -1) {
+ yyerror("unknown group %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = gid;
+ }
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 >= GID_MAX) {
+ yyerror("illegal gid value %lu", $1);
+ YYERROR;
+ }
+ $$ = $1;
+ }
+ ;
+
+flag : STRING {
+ int f;
+
+ if ((f = parse_flags($1)) < 0) {
+ yyerror("bad flags %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$.b1 = f;
+ }
+ ;
+
+flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; }
+ | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; }
+ | FLAGS ANY { $$.b1 = 0; $$.b2 = 0; }
+ ;
+
+icmpspec : ICMPTYPE icmp_item { $$ = $2; }
+ | ICMPTYPE '{' optnl icmp_list '}' { $$ = $4; }
+ | ICMP6TYPE icmp6_item { $$ = $2; }
+ | ICMP6TYPE '{' optnl icmp6_list '}' { $$ = $4; }
+ ;
+
+icmp_list : icmp_item optnl { $$ = $1; }
+ | icmp_list comma icmp_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+icmp6_list : icmp6_item optnl { $$ = $1; }
+ | icmp6_list comma icmp6_item optnl {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+icmp_item : icmptype {
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = 0;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmptype CODE STRING {
+ const struct icmpcodeent *p;
+
+ if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) {
+ yyerror("unknown icmp-code %s", $3);
+ free($3);
+ YYERROR;
+ }
+
+ free($3);
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = p->code + 1;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmptype CODE NUMBER {
+ if ($3 < 0 || $3 > 255) {
+ yyerror("illegal icmp-code %lu", $3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = $3 + 1;
+ $$->proto = IPPROTO_ICMP;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+icmp6_item : icmp6type {
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = 0;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmp6type CODE STRING {
+ const struct icmpcodeent *p;
+
+ if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) {
+ yyerror("unknown icmp6-code %s", $3);
+ free($3);
+ YYERROR;
+ }
+ free($3);
+
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = p->code + 1;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | icmp6type CODE NUMBER {
+ if ($3 < 0 || $3 > 255) {
+ yyerror("illegal icmp-code %lu", $3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_icmp));
+ if ($$ == NULL)
+ err(1, "icmp_item: calloc");
+ $$->type = $1;
+ $$->code = $3 + 1;
+ $$->proto = IPPROTO_ICMPV6;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+icmptype : STRING {
+ const struct icmptypeent *p;
+
+ if ((p = geticmptypebyname($1, AF_INET)) == NULL) {
+ yyerror("unknown icmp-type %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->type + 1;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("illegal icmp-type %lu", $1);
+ YYERROR;
+ }
+ $$ = $1 + 1;
+ }
+ ;
+
+icmp6type : STRING {
+ const struct icmptypeent *p;
+
+ if ((p = geticmptypebyname($1, AF_INET6)) ==
+ NULL) {
+ yyerror("unknown icmp6-type %s", $1);
+ free($1);
+ YYERROR;
+ }
+ $$ = p->type + 1;
+ free($1);
+ }
+ | NUMBER {
+ if ($1 < 0 || $1 > 255) {
+ yyerror("illegal icmp6-type %lu", $1);
+ YYERROR;
+ }
+ $$ = $1 + 1;
+ }
+ ;
+
+tos : STRING {
+ int val;
+ char *end;
+
+ if (map_tos($1, &val))
+ $$ = val;
+ else if ($1[0] == '0' && $1[1] == 'x') {
+ errno = 0;
+ $$ = strtoul($1, &end, 16);
+ if (errno || *end != '\0')
+ $$ = 256;
+ } else
+ $$ = 256; /* flag bad argument */
+ if ($$ < 0 || $$ > 255) {
+ yyerror("illegal tos value %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | NUMBER {
+ $$ = $1;
+ if ($$ < 0 || $$ > 255) {
+ yyerror("illegal tos value %lu", $1);
+ YYERROR;
+ }
+ }
+ ;
+
+sourcetrack : SOURCETRACK { $$ = PF_SRCTRACK; }
+ | SOURCETRACK GLOBAL { $$ = PF_SRCTRACK_GLOBAL; }
+ | SOURCETRACK RULE { $$ = PF_SRCTRACK_RULE; }
+ ;
+
+statelock : IFBOUND {
+ $$ = PFRULE_IFBOUND;
+ }
+ | FLOATING {
+ $$ = 0;
+ }
+ ;
+
+keep : NO STATE {
+ $$.action = 0;
+ $$.options = NULL;
+ }
+ | KEEP STATE state_opt_spec {
+ $$.action = PF_STATE_NORMAL;
+ $$.options = $3;
+ }
+ | MODULATE STATE state_opt_spec {
+ $$.action = PF_STATE_MODULATE;
+ $$.options = $3;
+ }
+ | SYNPROXY STATE state_opt_spec {
+ $$.action = PF_STATE_SYNPROXY;
+ $$.options = $3;
+ }
+ ;
+
+flush : /* empty */ { $$ = 0; }
+ | FLUSH { $$ = PF_FLUSH; }
+ | FLUSH GLOBAL {
+ $$ = PF_FLUSH | PF_FLUSH_GLOBAL;
+ }
+ ;
+
+state_opt_spec : '(' state_opt_list ')' { $$ = $2; }
+ | /* empty */ { $$ = NULL; }
+ ;
+
+state_opt_list : state_opt_item { $$ = $1; }
+ | state_opt_list comma state_opt_item {
+ $1->tail->next = $3;
+ $1->tail = $3;
+ $$ = $1;
+ }
+ ;
+
+state_opt_item : MAXIMUM NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX;
+ $$->data.max_states = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | NOSYNC {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_NOSYNC;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCSTATES NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_STATES;
+ $$->data.max_src_states = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCCONN NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_CONN;
+ $$->data.max_src_conn = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCCONNRATE NUMBER '/' NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX ||
+ $4 < 0 || $4 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_CONN_RATE;
+ $$->data.max_src_conn_rate.limit = $2;
+ $$->data.max_src_conn_rate.seconds = $4;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | OVERLOAD '<' STRING '>' flush {
+ if (strlen($3) >= PF_TABLE_NAME_SIZE) {
+ yyerror("table name '%s' too long", $3);
+ free($3);
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ if (strlcpy($$->data.overload.tblname, $3,
+ PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE)
+ errx(1, "state_opt_item: strlcpy");
+ free($3);
+ $$->type = PF_STATE_OPT_OVERLOAD;
+ $$->data.overload.flush = $5;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | MAXSRCNODES NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_MAX_SRC_NODES;
+ $$->data.max_src_nodes = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | sourcetrack {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_SRCTRACK;
+ $$->data.src_track = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | statelock {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_STATELOCK;
+ $$->data.statelock = $1;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | SLOPPY {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_SLOPPY;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | PFLOW {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_PFLOW;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | ALLOW_RELATED {
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_ALLOW_RELATED;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | STRING NUMBER {
+ int i;
+
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ for (i = 0; pf_timeouts[i].name &&
+ strcmp(pf_timeouts[i].name, $1); ++i)
+ ; /* nothing */
+ if (!pf_timeouts[i].name) {
+ yyerror("illegal timeout name %s", $1);
+ free($1);
+ YYERROR;
+ }
+ if (strchr(pf_timeouts[i].name, '.') == NULL) {
+ yyerror("illegal state timeout %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ $$ = calloc(1, sizeof(struct node_state_opt));
+ if ($$ == NULL)
+ err(1, "state_opt_item: calloc");
+ $$->type = PF_STATE_OPT_TIMEOUT;
+ $$->data.timeout.number = pf_timeouts[i].timeout;
+ $$->data.timeout.seconds = $2;
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ ;
+
+label : LABEL STRING {
+ $$ = $2;
+ }
+ ;
+
+etherqname : QUEUE STRING {
+ $$.qname = $2;
+ }
+ | QUEUE '(' STRING ')' {
+ $$.qname = $3;
+ }
+ ;
+
+qname : QUEUE STRING {
+ $$.qname = $2;
+ $$.pqname = NULL;
+ }
+ | QUEUE '(' STRING ')' {
+ $$.qname = $3;
+ $$.pqname = NULL;
+ }
+ | QUEUE '(' STRING comma STRING ')' {
+ $$.qname = $3;
+ $$.pqname = $5;
+ }
+ ;
+
+no : /* empty */ { $$ = 0; }
+ | NO { $$ = 1; }
+ ;
+
+portstar : numberstring {
+ if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) {
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+redir_host : host { $$ = $1; }
+ | '{' optnl redir_host_list '}' { $$ = $3; }
+ ;
+
+redir_host_list : host optnl { $$ = $1; }
+ | redir_host_list comma host optnl {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ ;
+
+/* Redirection without port */
+no_port_redirspec: redir_host pool_opts {
+ $$ = calloc(1, sizeof(struct redirspec));
+ if ($$ == NULL)
+ err(1, "redirspec: calloc");
+ $$->host = $1;
+ $$->pool_opts = $2;
+ $$->rport.a = $$->rport.b = $$->rport.t = 0;
+ }
+ ;
+
+/* Redirection with optional port */
+port_redirspec : no_port_redirspec;
+ | redir_host PORT portstar pool_opts {
+ $$ = calloc(1, sizeof(struct redirspec));
+ if ($$ == NULL)
+ err(1, "redirspec: calloc");
+ $$->host = $1;
+ $$->rport = $3;
+ $$->pool_opts = $4;
+ }
+
+/* Redirection with an arrow and an optional port: FreeBSD NAT rules */
+nat_redirspec : /* empty */ { $$ = NULL; }
+ | ARROW port_redirspec {
+ $$ = $2;
+ }
+ ;
+
+/* Redirection with interfaces and without ports: route-to rules */
+route_redirspec : routespec pool_opts {
+ $$ = calloc(1, sizeof(struct redirspec));
+ if ($$ == NULL)
+ err(1, "redirspec: calloc");
+ $$->host = $1;
+ $$->pool_opts = $2;
+ }
+ ;
+
+hashkey : /* empty */
+ {
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+ $$->key32[0] = arc4random();
+ $$->key32[1] = arc4random();
+ $$->key32[2] = arc4random();
+ $$->key32[3] = arc4random();
+ }
+ | string
+ {
+ if (!strncmp($1, "0x", 2)) {
+ if (strlen($1) != 34) {
+ free($1);
+ yyerror("hex key must be 128 bits "
+ "(32 hex digits) long");
+ YYERROR;
+ }
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+
+ if (sscanf($1, "0x%8x%8x%8x%8x",
+ &$$->key32[0], &$$->key32[1],
+ &$$->key32[2], &$$->key32[3]) != 4) {
+ free($$);
+ free($1);
+ yyerror("invalid hex key");
+ YYERROR;
+ }
+ } else {
+ MD5_CTX context;
+
+ $$ = calloc(1, sizeof(struct pf_poolhashkey));
+ if ($$ == NULL)
+ err(1, "hashkey: calloc");
+ MD5Init(&context);
+ MD5Update(&context, (unsigned char *)$1,
+ strlen($1));
+ MD5Final((unsigned char *)$$, &context);
+ HTONL($$->key32[0]);
+ HTONL($$->key32[1]);
+ HTONL($$->key32[2]);
+ HTONL($$->key32[3]);
+ }
+ free($1);
+ }
+ ;
+
+pool_opts : { bzero(&pool_opts, sizeof pool_opts); }
+ pool_opts_l
+ { $$ = pool_opts; }
+ | /* empty */ {
+ bzero(&pool_opts, sizeof pool_opts);
+ $$ = pool_opts;
+ }
+ ;
+
+pool_opts_l : pool_opts_l pool_opt
+ | pool_opt
+ ;
+
+pool_opt : BITMASK {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_BITMASK;
+ }
+ | RANDOM {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_RANDOM;
+ }
+ | SOURCEHASH hashkey {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_SRCHASH;
+ pool_opts.key = $2;
+ }
+ | ROUNDROBIN {
+ if (pool_opts.type) {
+ yyerror("pool type cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.type = PF_POOL_ROUNDROBIN;
+ }
+ | STATICPORT {
+ if (pool_opts.staticport) {
+ yyerror("static-port cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.staticport = 1;
+ }
+ | STICKYADDRESS {
+ if (pool_opts.marker & POM_STICKYADDRESS) {
+ yyerror("sticky-address cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.marker |= POM_STICKYADDRESS;
+ pool_opts.opts |= PF_POOL_STICKYADDR;
+ }
+ | ENDPI {
+ if (pool_opts.marker & POM_ENDPI) {
+ yyerror("endpoint-independent cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.marker |= POM_ENDPI;
+ pool_opts.opts |= PF_POOL_ENDPI;
+ }
+ | IPV6NH {
+ if (pool_opts.marker & POM_IPV6NH) {
+ yyerror("prefer-ipv6-nexthop cannot be redefined");
+ YYERROR;
+ }
+ pool_opts.marker |= POM_IPV6NH;
+ pool_opts.opts |= PF_POOL_IPV6NH;
+ }
+ | MAPEPORTSET number '/' number '/' number {
+ if (pool_opts.mape.offset) {
+ yyerror("map-e-portset cannot be redefined");
+ YYERROR;
+ }
+ if (pool_opts.type) {
+ yyerror("map-e-portset cannot be used with "
+ "address pools");
+ YYERROR;
+ }
+ if ($2 <= 0 || $2 >= 16) {
+ yyerror("MAP-E PSID offset must be 1-15");
+ YYERROR;
+ }
+ if ($4 < 0 || $4 >= 16 || $2 + $4 > 16) {
+ yyerror("Invalid MAP-E PSID length");
+ YYERROR;
+ } else if ($4 == 0) {
+ yyerror("PSID Length = 0: this means"
+ " you do not need MAP-E");
+ YYERROR;
+ }
+ if ($6 < 0 || $6 > 65535) {
+ yyerror("Invalid MAP-E PSID");
+ YYERROR;
+ }
+ pool_opts.mape.offset = $2;
+ pool_opts.mape.psidlen = $4;
+ pool_opts.mape.psid = $6;
+ }
+ ;
+
+binat_redirspec : /* empty */ { $$ = NULL; }
+ | ARROW host {
+ $$ = calloc(1, sizeof(struct redirspec));
+ if ($$ == NULL)
+ err(1, "redirspec: calloc");
+ $$->host = $2;
+ $$->rport.a = $$->rport.b = $$->rport.t = 0;
+ }
+ | ARROW host PORT portstar {
+ $$ = calloc(1, sizeof(struct redirspec));
+ if ($$ == NULL)
+ err(1, "redirspec: calloc");
+ $$->host = $2;
+ $$->rport = $4;
+ }
+ ;
+
+natpasslog : /* empty */ { $$.b1 = $$.b2 = 0; $$.w2 = 0; }
+ | PASS { $$.b1 = 1; $$.b2 = 0; $$.w2 = 0; }
+ | PASS log { $$.b1 = 1; $$.b2 = $2.log; $$.w2 = $2.logif; }
+ | log { $$.b1 = 0; $$.b2 = $1.log; $$.w2 = $1.logif; }
+ ;
+
+nataction : no NAT natpasslog {
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ $$.b1 = PF_NONAT;
+ else
+ $$.b1 = PF_NAT;
+ $$.b2 = $3.b1;
+ $$.w = $3.b2;
+ $$.w2 = $3.w2;
+ }
+ | no RDR natpasslog {
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ $$.b1 = PF_NORDR;
+ else
+ $$.b1 = PF_RDR;
+ $$.b2 = $3.b1;
+ $$.w = $3.b2;
+ $$.w2 = $3.w2;
+ }
+ ;
+
+natrule : nataction interface af proto fromto tag tagged rtable
+ nat_redirspec
+ {
+ struct pfctl_rule r;
+ struct node_state_opt *o;
+
+ if (check_rulestate(PFCTL_STATE_NAT))
+ YYERROR;
+
+ pfctl_init_rule(&r);
+
+ r.action = $1.b1;
+ r.natpass = $1.b2;
+ r.log = $1.w;
+ r.logif = $1.w2;
+ r.af = $3;
+
+ if (!r.af) {
+ if ($5.src.host && $5.src.host->af &&
+ !$5.src.host->ifindex)
+ r.af = $5.src.host->af;
+ else if ($5.dst.host && $5.dst.host->af &&
+ !$5.dst.host->ifindex)
+ r.af = $5.dst.host->af;
+ }
+
+ if ($6 != NULL)
+ if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >=
+ PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+
+ if ($7.name)
+ if (strlcpy(r.match_tagname, $7.name,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ r.match_tag_not = $7.neg;
+ r.rtableid = $8;
+
+ if (r.action == PF_NONAT || r.action == PF_NORDR) {
+ if ($9 != NULL) {
+ yyerror("translation rule with 'no' "
+ "does not need '->'");
+ YYERROR;
+ }
+ } else {
+ if ($9 == NULL || $9->host == NULL) {
+ yyerror("translation rule requires '-> "
+ "address'");
+ YYERROR;
+ }
+ if ($9->pool_opts.opts & PF_POOL_IPV6NH) {
+ yyerror("The prefer-ipv6-nexthop option "
+ "can't be used for nat/rdr/binat pools"
+ );
+ YYERROR;
+ }
+ if (!r.af && ! $9->host->ifindex)
+ r.af = $9->host->af;
+
+ remove_invalid_hosts(&$9->host, &r.af);
+ if (invalid_redirect($9->host, r.af))
+ YYERROR;
+ if ($9->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9->host = gen_dynnode($9->host, r.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9->host, r.af))
+ YYERROR;
+ }
+
+ o = keep_state_defaults;
+ while (o) {
+ switch (o->type) {
+ case PF_STATE_OPT_PFLOW:
+ if (r.rule_flag & PFRULE_PFLOW) {
+ yyerror("state pflow option: "
+ "multiple definitions");
+ YYERROR;
+ }
+ r.rule_flag |= PFRULE_PFLOW;
+ break;
+ }
+ o = o->next;
+ }
+
+ expand_rule(&r, false, $2, NULL, $9, NULL, $4,
+ $5.src_os, $5.src.host, $5.src.port, $5.dst.host,
+ $5.dst.port, 0, 0, 0, 0);
+ }
+ ;
+
+binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag
+ tagged rtable binat_redirspec
+ {
+ struct pfctl_rule binat;
+ struct pfctl_pooladdr *pa;
+
+ if (check_rulestate(PFCTL_STATE_NAT))
+ YYERROR;
+ if (disallow_urpf_failed($9, "\"urpf-failed\" is not "
+ "permitted as a binat destination"))
+ YYERROR;
+
+ pfctl_init_rule(&binat);
+
+ if ($1 && $3.b1) {
+ yyerror("\"pass\" not valid with \"no\"");
+ YYERROR;
+ }
+ if ($1)
+ binat.action = PF_NOBINAT;
+ else
+ binat.action = PF_BINAT;
+ binat.natpass = $3.b1;
+ binat.log = $3.b2;
+ binat.logif = $3.w2;
+ binat.af = $5;
+ if (!binat.af && $8 != NULL && $8->af)
+ binat.af = $8->af;
+ if (!binat.af && $9 != NULL && $9->af)
+ binat.af = $9->af;
+
+ if (!binat.af && $13 != NULL && $13->host)
+ binat.af = $13->host->af;
+ if (!binat.af) {
+ yyerror("address family (inet/inet6) "
+ "undefined");
+ YYERROR;
+ }
+
+ if ($4 != NULL) {
+ memcpy(binat.ifname, $4->ifname,
+ sizeof(binat.ifname));
+ binat.ifnot = $4->not;
+ free($4);
+ }
+
+ if ($10 != NULL)
+ if (strlcpy(binat.tagname, $10,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ if ($11.name)
+ if (strlcpy(binat.match_tagname, $11.name,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ YYERROR;
+ }
+ binat.match_tag_not = $11.neg;
+ binat.rtableid = $12;
+
+ if ($6 != NULL) {
+ binat.proto = $6->proto;
+ free($6);
+ }
+
+ if ($8 != NULL && disallow_table($8, "invalid use of "
+ "table <%s> as the source address of a binat rule"))
+ YYERROR;
+ if ($8 != NULL && disallow_alias($8, "invalid use of "
+ "interface (%s) as the source address of a binat "
+ "rule"))
+ YYERROR;
+ if ($13 != NULL && $13->host != NULL && disallow_table(
+ $13->host, "invalid use of table <%s> as the "
+ "redirect address of a binat rule"))
+ YYERROR;
+ if ($13 != NULL && $13->host != NULL && disallow_alias(
+ $13->host, "invalid use of interface (%s) as the "
+ "redirect address of a binat rule"))
+ YYERROR;
+
+ if ($8 != NULL) {
+ if ($8->next) {
+ yyerror("multiple binat ip addresses");
+ YYERROR;
+ }
+ if ($8->addr.type == PF_ADDR_DYNIFTL)
+ $8->af = binat.af;
+ if ($8->af != binat.af) {
+ yyerror("binat ip versions must match");
+ YYERROR;
+ }
+ if ($8->addr.type == PF_ADDR_DYNIFTL) {
+ if (($8 = gen_dynnode($8, binat.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($8, binat.af))
+ YYERROR;
+ memcpy(&binat.src.addr, &$8->addr,
+ sizeof(binat.src.addr));
+ free($8);
+ }
+ if ($9 != NULL) {
+ if ($9->next) {
+ yyerror("multiple binat ip addresses");
+ YYERROR;
+ }
+ if ($9->af != binat.af && $9->af) {
+ yyerror("binat ip versions must match");
+ YYERROR;
+ }
+ if ($9->addr.type == PF_ADDR_DYNIFTL) {
+ if (($9 = gen_dynnode($9, binat.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($9, binat.af))
+ YYERROR;
+ memcpy(&binat.dst.addr, &$9->addr,
+ sizeof(binat.dst.addr));
+ binat.dst.neg = $9->not;
+ free($9);
+ }
+
+ if (binat.action == PF_NOBINAT) {
+ if ($13 != NULL) {
+ yyerror("'no binat' rule does not need"
+ " '->'");
+ YYERROR;
+ }
+ } else {
+ if ($13 == NULL || $13->host == NULL) {
+ yyerror("'binat' rule requires"
+ " '-> address'");
+ YYERROR;
+ }
+
+ remove_invalid_hosts(&$13->host, &binat.af);
+ if (invalid_redirect($13->host, binat.af))
+ YYERROR;
+ if ($13->host->next != NULL) {
+ yyerror("binat rule must redirect to "
+ "a single address");
+ YYERROR;
+ }
+ if ($13->host->addr.type == PF_ADDR_DYNIFTL) {
+ if (($13->host = gen_dynnode($13->host, binat.af)) == NULL)
+ err(1, "calloc");
+ }
+ if (check_netmask($13->host, binat.af))
+ YYERROR;
+
+ if (!PF_AZERO(&binat.src.addr.v.a.mask,
+ binat.af) &&
+ !PF_AEQ(&binat.src.addr.v.a.mask,
+ &$13->host->addr.v.a.mask, binat.af)) {
+ yyerror("'binat' source mask and "
+ "redirect mask must be the same");
+ YYERROR;
+ }
+
+ pa = calloc(1, sizeof(struct pfctl_pooladdr));
+ if (pa == NULL)
+ err(1, "binat: calloc");
+ pa->addr = $13->host->addr;
+ pa->ifname[0] = 0;
+ pa->af = $13->host->af;
+ TAILQ_INSERT_TAIL(&binat.rdr.list,
+ pa, entries);
+
+ free($13);
+ }
+
+ pfctl_append_rule(pf, &binat);
+ }
+ ;
+
+tag : /* empty */ { $$ = NULL; }
+ | TAG STRING { $$ = $2; }
+ ;
+
+tagged : /* empty */ { $$.neg = 0; $$.name = NULL; }
+ | not TAGGED string { $$.neg = $1; $$.name = $3; }
+ ;
+
+rtable : /* empty */ { $$ = -1; }
+ | RTABLE NUMBER {
+ if ($2 < 0 || $2 > rt_tableid_max()) {
+ yyerror("invalid rtable id");
+ YYERROR;
+ }
+ $$ = $2;
+ }
+ ;
+
+route_host : STRING {
+ $$ = calloc(1, sizeof(struct node_host));
+ if ($$ == NULL)
+ err(1, "route_host: calloc");
+ if (strlen($1) >= IFNAMSIZ) {
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ $$->ifname = strdup($1);
+ set_ipmask($$, 128);
+ $$->next = NULL;
+ $$->tail = $$;
+ }
+ | '(' STRING host ')' {
+ struct node_host *n;
+
+ $$ = $3;
+ for (n = $3; n != NULL; n = n->next) {
+ if (strlen($2) >= IFNAMSIZ) {
+ yyerror("interface name too long");
+ YYERROR;
+ }
+ n->ifname = strdup($2);
+ }
+ }
+ ;
+
+route_host_list : route_host optnl { $$ = $1; }
+ | route_host_list comma route_host optnl {
+ $1->tail->next = $3;
+ $1->tail = $3->tail;
+ $$ = $1;
+ }
+ ;
+
+routespec : route_host { $$ = $1; }
+ | '{' optnl route_host_list '}' { $$ = $3; }
+ ;
+
+route : /* empty */ {
+ $$.rt = PF_NOPFROUTE;
+ }
+ | FASTROUTE {
+ /* backwards-compat */
+ $$.rt = PF_NOPFROUTE;
+ }
+ | ROUTETO route_redirspec {
+ $$.rt = PF_ROUTETO;
+ $$.redirspec = $2;
+ }
+ | REPLYTO route_redirspec {
+ $$.rt = PF_REPLYTO;
+ $$.redirspec = $2;
+ }
+ | DUPTO route_redirspec {
+ $$.rt = PF_DUPTO;
+ $$.redirspec = $2;
+ }
+ ;
+
+timeout_spec : STRING NUMBER
+ {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($1);
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (pfctl_apply_timeout(pf, $1, $2, 0) != 0) {
+ yyerror("unknown timeout %s", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ | INTERVAL NUMBER {
+ if (check_rulestate(PFCTL_STATE_OPTION))
+ YYERROR;
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (pfctl_apply_timeout(pf, "interval", $2, 0) != 0)
+ YYERROR;
+ }
+ ;
+
+timeout_list : timeout_list comma timeout_spec optnl
+ | timeout_spec optnl
+ ;
+
+limit_spec : STRING NUMBER
+ {
+ if (check_rulestate(PFCTL_STATE_OPTION)) {
+ free($1);
+ YYERROR;
+ }
+ if ($2 < 0 || $2 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (pfctl_apply_limit(pf, $1, $2) != 0) {
+ yyerror("unable to set limit %s %u", $1, $2);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+limit_list : limit_list comma limit_spec optnl
+ | limit_spec optnl
+ ;
+
+comma : ','
+ | /* empty */
+ ;
+
+yesno : NO { $$ = 0; }
+ | STRING {
+ if (!strcmp($1, "yes"))
+ $$ = 1;
+ else {
+ yyerror("invalid value '%s', expected 'yes' "
+ "or 'no'", $1);
+ free($1);
+ YYERROR;
+ }
+ free($1);
+ }
+ ;
+
+unaryop : '=' { $$ = PF_OP_EQ; }
+ | NE { $$ = PF_OP_NE; }
+ | LE { $$ = PF_OP_LE; }
+ | '<' { $$ = PF_OP_LT; }
+ | GE { $$ = PF_OP_GE; }
+ | '>' { $$ = PF_OP_GT; }
+ ;
+
+%%
+
+int
+yyerror(const char *fmt, ...)
+{
+ va_list ap;
+
+ file->errors++;
+ va_start(ap, fmt);
+ fprintf(stderr, "%s:%d: ", file->name, yylval.lineno);
+ vfprintf(stderr, fmt, ap);
+ fprintf(stderr, "\n");
+ va_end(ap);
+ return (0);
+}
+
+int
+validate_range(uint8_t op, uint16_t p1, uint16_t p2)
+{
+ uint16_t a = ntohs(p1);
+ uint16_t b = ntohs(p2);
+
+ if ((op == PF_OP_RRG && a > b) || /* 34:12, i.e. none */
+ (op == PF_OP_IRG && a >= b) || /* 34><12, i.e. none */
+ (op == PF_OP_XRG && a > b)) /* 34<>22, i.e. all */
+ return 1;
+ return 0;
+}
+
+int
+disallow_table(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (h->addr.type == PF_ADDR_TABLE) {
+ yyerror(fmt, h->addr.v.tblname);
+ return (1);
+ }
+ return (0);
+}
+
+int
+disallow_urpf_failed(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (h->addr.type == PF_ADDR_URPFFAILED) {
+ yyerror(fmt);
+ return (1);
+ }
+ return (0);
+}
+
+int
+disallow_alias(struct node_host *h, const char *fmt)
+{
+ for (; h != NULL; h = h->next)
+ if (DYNIF_MULTIADDR(h->addr)) {
+ yyerror(fmt, h->addr.v.tblname);
+ return (1);
+ }
+ return (0);
+}
+
+int
+rule_consistent(struct pfctl_rule *r)
+{
+ int problems = 0;
+
+ switch (r->action) {
+ case PF_PASS:
+ case PF_MATCH:
+ case PF_DROP:
+ case PF_SCRUB:
+ case PF_NOSCRUB:
+ problems = filter_consistent(r);
+ break;
+ case PF_NAT:
+ case PF_NONAT:
+ problems = nat_consistent(r);
+ break;
+ case PF_RDR:
+ case PF_NORDR:
+ problems = rdr_consistent(r);
+ break;
+ case PF_BINAT:
+ case PF_NOBINAT:
+ default:
+ break;
+ }
+ return (problems);
+}
+
+int
+filter_consistent(struct pfctl_rule *r)
+{
+ int problems = 0;
+
+ if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP &&
+ r->proto != IPPROTO_SCTP &&
+ (r->src.port_op || r->dst.port_op)) {
+ yyerror("port only applies to tcp/udp/sctp");
+ problems++;
+ }
+ if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 &&
+ (r->type || r->code)) {
+ yyerror("icmp-type/code only applies to icmp");
+ problems++;
+ }
+ if (!r->af && (r->type || r->code)) {
+ yyerror("must indicate address family with icmp-type/code");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_AFTO && r->af == r->naf) {
+ yyerror("must indicate different address family with af-to");
+ problems++;
+ }
+ if (r->overload_tblname[0] &&
+ r->max_src_conn == 0 && r->max_src_conn_rate.seconds == 0) {
+ yyerror("'overload' requires 'max-src-conn' "
+ "or 'max-src-conn-rate'");
+ problems++;
+ }
+ if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) ||
+ (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) {
+ yyerror("proto %s doesn't match address family %s",
+ r->proto == IPPROTO_ICMP ? "icmp" : "icmp6",
+ r->af == AF_INET ? "inet" : "inet6");
+ problems++;
+ }
+ if (r->allow_opts && r->action != PF_PASS && r->action != PF_MATCH) {
+ yyerror("allow-opts can only be specified for pass or "
+ "match rules");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op ||
+ r->dst.port_op || r->flagset || r->type || r->code)) {
+ yyerror("fragments can be filtered only on IP header fields");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) {
+ yyerror("return-rst can only be applied to TCP rules");
+ problems++;
+ }
+ if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) {
+ yyerror("max-src-nodes requires 'source-track rule'");
+ problems++;
+ }
+ if (r->action != PF_PASS && r->keep_state) {
+ yyerror("keep state is great, but only for pass rules");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_STATESLOPPY &&
+ (r->keep_state == PF_STATE_MODULATE ||
+ r->keep_state == PF_STATE_SYNPROXY)) {
+ yyerror("sloppy state matching cannot be used with "
+ "synproxy state or modulate state");
+ problems++;
+ }
+ if ((r->keep_state == PF_STATE_SYNPROXY) && (r->direction != PF_IN))
+ fprintf(stderr, "%s:%d: warning: "
+ "synproxy used for inbound rules only, "
+ "ignored for outbound\n", file->name, yylval.lineno);
+ if (r->rule_flag & PFRULE_AFTO && r->rt) {
+ if (r->rt != PF_ROUTETO && r->rt != PF_REPLYTO) {
+ yyerror("dup-to "
+ "must not be used on af-to rules");
+ problems++;
+ }
+ }
+ /* Basic rule sanity check. */
+ switch (r->action) {
+ case PF_MATCH:
+ if (r->divert.port) {
+ yyerror("divert is not supported on match rules");
+ problems++;
+ }
+ if (r->rt) {
+ yyerror("route-to, reply-to, dup-to and fastroute "
+ "must not be used on match rules");
+ problems++;
+ }
+ if (r->rule_flag & PFRULE_AFTO) {
+ yyerror("af-to is not supported on match rules");
+ problems++;
+ }
+ break;
+ case PF_DROP:
+ if (r->rt) {
+ yyerror("route-to, reply-to and dup-to "
+ "are not supported on block rules");
+ problems++;
+ }
+ break;
+ default:;
+ }
+ if (!TAILQ_EMPTY(&(r->nat.list)) || !TAILQ_EMPTY(&(r->rdr.list))) {
+ if (r->action != PF_MATCH && !r->keep_state) {
+ yyerror("nat-to and rdr-to require keep state");
+ problems++;
+ }
+ if (r->direction == PF_INOUT) {
+ yyerror("nat-to and rdr-to require a direction");
+ problems++;
+ }
+ }
+ if (r->route.opts & PF_POOL_STICKYADDR && !r->keep_state) {
+ yyerror("'sticky-address' requires 'keep state'");
+ }
+ return (-problems);
+}
+
+int
+nat_consistent(struct pfctl_rule *r)
+{
+ return (0); /* yeah! */
+}
+
+int
+rdr_consistent(struct pfctl_rule *r)
+{
+ int problems = 0;
+
+ if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP &&
+ r->proto != IPPROTO_SCTP) {
+ if (r->src.port_op) {
+ yyerror("src port only applies to tcp/udp/sctp");
+ problems++;
+ }
+ if (r->dst.port_op) {
+ yyerror("dst port only applies to tcp/udp/sctp");
+ problems++;
+ }
+ if (r->rdr.proxy_port[0]) {
+ yyerror("rdr port only applies to tcp/udp/sctp");
+ problems++;
+ }
+ }
+ if (r->dst.port_op &&
+ r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) {
+ yyerror("invalid port operator for rdr destination port");
+ problems++;
+ }
+ return (-problems);
+}
+
+int
+process_tabledef(char *name, struct table_opts *opts, int popts)
+{
+ struct pfr_buffer ab;
+ struct node_tinit *ti;
+ struct pfr_uktable *ukt;
+ unsigned long maxcount;
+ size_t s = sizeof(maxcount);
+
+ bzero(&ab, sizeof(ab));
+ ab.pfrb_type = PFRB_ADDRS;
+ SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) {
+ if (ti->file)
+ if (pfr_buf_load(&ab, ti->file, 0, append_addr, popts)) {
+ if (errno)
+ yyerror("cannot load \"%s\": %s",
+ ti->file, strerror(errno));
+ else
+ yyerror("file \"%s\" contains bad data",
+ ti->file);
+ goto _error;
+ }
+ if (ti->host)
+ if (append_addr_host(&ab, ti->host, 0, 0)) {
+ yyerror("cannot create address buffer: %s",
+ strerror(errno));
+ goto _error;
+ }
+ }
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(name, opts->flags, opts->init_addr,
+ &opts->init_nodes);
+ if (!(pf->opts & PF_OPT_NOACTION) ||
+ (pf->opts & PF_OPT_DUMMYACTION))
+ warn_duplicate_tables(name, pf->anchor->path);
+ else if (pf->opts & PF_OPT_VERBOSE)
+ fprintf(stderr, "%s:%d: skipping duplicate table checks"
+ " for <%s>\n", file->name, yylval.lineno, name);
+ /*
+ * postpone definition of non-root tables to moment
+ * when path is fully resolved.
+ */
+ if (pf->asd > 0) {
+ ukt = calloc(1, sizeof(struct pfr_uktable));
+ if (ukt == NULL) {
+ DBGPRINT(
+ "%s:%d: not enough memory for <%s>\n", file->name,
+ yylval.lineno, name);
+ goto _error;
+ }
+ } else
+ ukt = NULL;
+ if (!(pf->opts & PF_OPT_NOACTION) &&
+ pfctl_define_table(name, opts->flags, opts->init_addr,
+ pf->anchor->path, &ab, pf->anchor->ruleset.tticket, ukt)) {
+
+ if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s,
+ NULL, 0) == -1)
+ maxcount = 65535;
+
+ if (ab.pfrb_size > maxcount)
+ yyerror("cannot define table %s: too many elements.\n"
+ "Consider increasing net.pf.request_maxcount.",
+ name);
+ else
+ yyerror("cannot define table %s: %s", name,
+ pf_strerror(errno));
+
+ goto _error;
+ }
+
+ if (ukt != NULL) {
+ ukt->pfrukt_init_addr = opts->init_addr;
+ if (RB_INSERT(pfr_ktablehead, &pfr_ktables,
+ &ukt->pfrukt_kt) != NULL) {
+ /*
+ * I think this should not happen, because
+ * pfctl_define_table() above does the same check
+ * effectively.
+ */
+ DBGPRINT(
+ "%s:%d table %s already exists in %s\n",
+ file->name, yylval.lineno,
+ ukt->pfrukt_name, pf->anchor->path);
+ free(ukt);
+ goto _error;
+ }
+ DBGPRINT("%s %s@%s inserted to tree\n",
+ __func__, ukt->pfrukt_name, pf->anchor->path);
+ } else
+ DBGPRINT("%s ukt is null\n", __func__);
+
+ pf->tdirty = 1;
+ pfr_buf_clear(&ab);
+ return (0);
+_error:
+ pfr_buf_clear(&ab);
+ return (-1);
+}
+
+struct keywords {
+ const char *k_name;
+ int k_val;
+};
+
+/* macro gore, but you should've seen the prior indentation nightmare... */
+
+#define FREE_LIST(T,r) \
+ do { \
+ T *p, *node = r; \
+ while (node != NULL) { \
+ p = node; \
+ node = node->next; \
+ free(p); \
+ } \
+ } while (0)
+
+#define LOOP_THROUGH(T,n,r,C) \
+ do { \
+ T *n; \
+ if (r == NULL) { \
+ r = calloc(1, sizeof(T)); \
+ if (r == NULL) \
+ err(1, "LOOP: calloc"); \
+ r->next = NULL; \
+ } \
+ n = r; \
+ while (n != NULL) { \
+ do { \
+ C; \
+ } while (0); \
+ n = n->next; \
+ } \
+ } while (0)
+
+void
+expand_label_str(char *label, size_t len, const char *srch, const char *repl)
+{
+ char *tmp;
+ char *p, *q;
+
+ if ((tmp = calloc(1, len)) == NULL)
+ err(1, "%s: calloc", __func__);
+ p = q = label;
+ while ((q = strstr(p, srch)) != NULL) {
+ *q = '\0';
+ if ((strlcat(tmp, p, len) >= len) ||
+ (strlcat(tmp, repl, len) >= len))
+ errx(1, "%s: label too long", __func__);
+ q += strlen(srch);
+ p = q;
+ }
+ if (strlcat(tmp, p, len) >= len)
+ errx(1, "%s: label too long", __func__);
+ strlcpy(label, tmp, len); /* always fits */
+ free(tmp);
+}
+
+void
+expand_label_if(const char *name, char *label, size_t len, const char *ifname)
+{
+ if (strstr(label, name) != NULL) {
+ if (!*ifname)
+ expand_label_str(label, len, name, "any");
+ else
+ expand_label_str(label, len, name, ifname);
+ }
+}
+
+void
+expand_label_addr(const char *name, char *label, size_t len, sa_family_t af,
+ struct pf_rule_addr *addr)
+{
+ char tmp[64], tmp_not[66];
+
+ if (strstr(label, name) != NULL) {
+ switch (addr->addr.type) {
+ case PF_ADDR_DYNIFTL:
+ snprintf(tmp, sizeof(tmp), "(%s)", addr->addr.v.ifname);
+ break;
+ case PF_ADDR_TABLE:
+ snprintf(tmp, sizeof(tmp), "<%s>", addr->addr.v.tblname);
+ break;
+ case PF_ADDR_NOROUTE:
+ snprintf(tmp, sizeof(tmp), "no-route");
+ break;
+ case PF_ADDR_URPFFAILED:
+ snprintf(tmp, sizeof(tmp), "urpf-failed");
+ break;
+ case PF_ADDR_ADDRMASK:
+ if (!af || (PF_AZERO(&addr->addr.v.a.addr, af) &&
+ PF_AZERO(&addr->addr.v.a.mask, af)))
+ snprintf(tmp, sizeof(tmp), "any");
+ else {
+ char a[48];
+ int bits;
+
+ if (inet_ntop(af, &addr->addr.v.a.addr, a,
+ sizeof(a)) == NULL)
+ snprintf(tmp, sizeof(tmp), "?");
+ else {
+ bits = unmask(&addr->addr.v.a.mask);
+ if ((af == AF_INET && bits < 32) ||
+ (af == AF_INET6 && bits < 128))
+ snprintf(tmp, sizeof(tmp),
+ "%s/%d", a, bits);
+ else
+ snprintf(tmp, sizeof(tmp),
+ "%s", a);
+ }
+ }
+ break;
+ default:
+ snprintf(tmp, sizeof(tmp), "?");
+ break;
+ }
+
+ if (addr->neg) {
+ snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp);
+ expand_label_str(label, len, name, tmp_not);
+ } else
+ expand_label_str(label, len, name, tmp);
+ }
+}
+
+void
+expand_label_port(const char *name, char *label, size_t len,
+ struct pf_rule_addr *addr)
+{
+ char a1[6], a2[6], op[13] = "";
+
+ if (strstr(label, name) != NULL) {
+ snprintf(a1, sizeof(a1), "%u", ntohs(addr->port[0]));
+ snprintf(a2, sizeof(a2), "%u", ntohs(addr->port[1]));
+ if (!addr->port_op)
+ ;
+ else if (addr->port_op == PF_OP_IRG)
+ snprintf(op, sizeof(op), "%s><%s", a1, a2);
+ else if (addr->port_op == PF_OP_XRG)
+ snprintf(op, sizeof(op), "%s<>%s", a1, a2);
+ else if (addr->port_op == PF_OP_EQ)
+ snprintf(op, sizeof(op), "%s", a1);
+ else if (addr->port_op == PF_OP_NE)
+ snprintf(op, sizeof(op), "!=%s", a1);
+ else if (addr->port_op == PF_OP_LT)
+ snprintf(op, sizeof(op), "<%s", a1);
+ else if (addr->port_op == PF_OP_LE)
+ snprintf(op, sizeof(op), "<=%s", a1);
+ else if (addr->port_op == PF_OP_GT)
+ snprintf(op, sizeof(op), ">%s", a1);
+ else if (addr->port_op == PF_OP_GE)
+ snprintf(op, sizeof(op), ">=%s", a1);
+ expand_label_str(label, len, name, op);
+ }
+}
+
+void
+expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto)
+{
+ const char *protoname;
+ char n[4];
+
+ if (strstr(label, name) != NULL) {
+ protoname = pfctl_proto2name(proto);
+ if (protoname != NULL)
+ expand_label_str(label, len, name, protoname);
+ else {
+ snprintf(n, sizeof(n), "%u", proto);
+ expand_label_str(label, len, name, n);
+ }
+ }
+}
+
+void
+expand_label_nr(const char *name, char *label, size_t len,
+ struct pfctl_rule *r)
+{
+ char n[11];
+
+ if (strstr(label, name) != NULL) {
+ snprintf(n, sizeof(n), "%u", r->nr);
+ expand_label_str(label, len, name, n);
+ }
+}
+
+void
+expand_label(char *label, size_t len, struct pfctl_rule *r)
+{
+ expand_label_if("$if", label, len, r->ifname);
+ expand_label_addr("$srcaddr", label, len, r->af, &r->src);
+ expand_label_addr("$dstaddr", label, len, r->af, &r->dst);
+ expand_label_port("$srcport", label, len, &r->src);
+ expand_label_port("$dstport", label, len, &r->dst);
+ expand_label_proto("$proto", label, len, r->proto);
+ expand_label_nr("$nr", label, len, r);
+}
+
+int
+expand_altq(struct pf_altq *a, struct node_if *interfaces,
+ struct node_queue *nqueues, struct node_queue_bw bwspec,
+ struct node_queue_opt *opts)
+{
+ struct pf_altq pa, pb;
+ char qname[PF_QNAME_SIZE];
+ struct node_queue *n;
+ struct node_queue_bw bw;
+ int errs = 0;
+
+ if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
+ FREE_LIST(struct node_if, interfaces);
+ if (nqueues)
+ FREE_LIST(struct node_queue, nqueues);
+ return (0);
+ }
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ memcpy(&pa, a, sizeof(struct pf_altq));
+ if (strlcpy(pa.ifname, interface->ifname,
+ sizeof(pa.ifname)) >= sizeof(pa.ifname))
+ errx(1, "%s: strlcpy", __func__);
+
+ if (interface->not) {
+ yyerror("altq on ! <interface> is not supported");
+ errs++;
+ } else {
+ if (eval_pfaltq(pf, &pa, &bwspec, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pa))
+ errs++;
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ print_altq(&pf->paltq->altq, 0,
+ &bwspec, opts);
+ if (nqueues && nqueues->tail) {
+ printf("queue { ");
+ LOOP_THROUGH(struct node_queue, queue,
+ nqueues,
+ printf("%s ",
+ queue->queue);
+ );
+ printf("}");
+ }
+ printf("\n");
+ }
+
+ if (pa.scheduler == ALTQT_CBQ ||
+ pa.scheduler == ALTQT_HFSC ||
+ pa.scheduler == ALTQT_FAIRQ) {
+ /* now create a root queue */
+ memset(&pb, 0, sizeof(struct pf_altq));
+ if (strlcpy(qname, "root_", sizeof(qname)) >=
+ sizeof(qname))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcat(qname, interface->ifname,
+ sizeof(qname)) >= sizeof(qname))
+ errx(1, "%s: strlcat", __func__);
+ if (strlcpy(pb.qname, qname,
+ sizeof(pb.qname)) >= sizeof(pb.qname))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(pb.ifname, interface->ifname,
+ sizeof(pb.ifname)) >= sizeof(pb.ifname))
+ errx(1, "%s: strlcpy", __func__);
+ pb.qlimit = pa.qlimit;
+ pb.scheduler = pa.scheduler;
+ bw.bw_absolute = pa.ifbandwidth;
+ bw.bw_percent = 0;
+ if (eval_pfqueue(pf, &pb, &bw, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pb))
+ errs++;
+ }
+
+ LOOP_THROUGH(struct node_queue, queue, nqueues,
+ n = calloc(1, sizeof(struct node_queue));
+ if (n == NULL)
+ err(1, "%s: calloc", __func__);
+ if (pa.scheduler == ALTQT_CBQ ||
+ pa.scheduler == ALTQT_HFSC ||
+ pa.scheduler == ALTQT_FAIRQ)
+ if (strlcpy(n->parent, qname,
+ sizeof(n->parent)) >=
+ sizeof(n->parent))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(n->queue, queue->queue,
+ sizeof(n->queue)) >= sizeof(n->queue))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(n->ifname, interface->ifname,
+ sizeof(n->ifname)) >= sizeof(n->ifname))
+ errx(1, "%s: strlcpy", __func__);
+ n->scheduler = pa.scheduler;
+ n->next = NULL;
+ n->tail = n;
+ if (queues == NULL)
+ queues = n;
+ else {
+ queues->tail->next = n;
+ queues->tail = n;
+ }
+ );
+ }
+ );
+ FREE_LIST(struct node_if, interfaces);
+ if (nqueues)
+ FREE_LIST(struct node_queue, nqueues);
+
+ return (errs);
+}
+
+int
+expand_queue(struct pf_altq *a, struct node_if *interfaces,
+ struct node_queue *nqueues, struct node_queue_bw bwspec,
+ struct node_queue_opt *opts)
+{
+ struct node_queue *n, *nq;
+ struct pf_altq pa;
+ u_int8_t found = 0;
+ u_int8_t errs = 0;
+
+ if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) {
+ FREE_LIST(struct node_queue, nqueues);
+ return (0);
+ }
+
+ if (queues == NULL) {
+ yyerror("queue %s has no parent", a->qname);
+ FREE_LIST(struct node_queue, nqueues);
+ return (1);
+ }
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ LOOP_THROUGH(struct node_queue, tqueue, queues,
+ if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) &&
+ (interface->ifname[0] == 0 ||
+ (!interface->not && !strncmp(interface->ifname,
+ tqueue->ifname, IFNAMSIZ)) ||
+ (interface->not && strncmp(interface->ifname,
+ tqueue->ifname, IFNAMSIZ)))) {
+ /* found ourself in queues */
+ found++;
+
+ memcpy(&pa, a, sizeof(struct pf_altq));
+
+ if (pa.scheduler != ALTQT_NONE &&
+ pa.scheduler != tqueue->scheduler) {
+ yyerror("exactly one scheduler type "
+ "per interface allowed");
+ return (1);
+ }
+ pa.scheduler = tqueue->scheduler;
+
+ /* scheduler dependent error checking */
+ switch (pa.scheduler) {
+ case ALTQT_PRIQ:
+ if (nqueues != NULL) {
+ yyerror("priq queues cannot "
+ "have child queues");
+ return (1);
+ }
+ if (bwspec.bw_absolute > 0 ||
+ bwspec.bw_percent < 100) {
+ yyerror("priq doesn't take "
+ "bandwidth");
+ return (1);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (strlcpy(pa.ifname, tqueue->ifname,
+ sizeof(pa.ifname)) >= sizeof(pa.ifname))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(pa.parent, tqueue->parent,
+ sizeof(pa.parent)) >= sizeof(pa.parent))
+ errx(1, "%s: strlcpy", __func__);
+
+ if (eval_pfqueue(pf, &pa, &bwspec, opts))
+ errs++;
+ else
+ if (pfctl_add_altq(pf, &pa))
+ errs++;
+
+ for (nq = nqueues; nq != NULL; nq = nq->next) {
+ if (!strcmp(a->qname, nq->queue)) {
+ yyerror("queue cannot have "
+ "itself as child");
+ errs++;
+ continue;
+ }
+ n = calloc(1,
+ sizeof(struct node_queue));
+ if (n == NULL)
+ err(1, "%s: calloc", __func__);
+ if (strlcpy(n->parent, a->qname,
+ sizeof(n->parent)) >=
+ sizeof(n->parent))
+ errx(1, "%s strlcpy", __func__);
+ if (strlcpy(n->queue, nq->queue,
+ sizeof(n->queue)) >=
+ sizeof(n->queue))
+ errx(1, "%s strlcpy", __func__);
+ if (strlcpy(n->ifname, tqueue->ifname,
+ sizeof(n->ifname)) >=
+ sizeof(n->ifname))
+ errx(1, "%s strlcpy", __func__);
+ n->scheduler = tqueue->scheduler;
+ n->next = NULL;
+ n->tail = n;
+ if (queues == NULL)
+ queues = n;
+ else {
+ queues->tail->next = n;
+ queues->tail = n;
+ }
+ }
+ if ((pf->opts & PF_OPT_VERBOSE) && (
+ (found == 1 && interface->ifname[0] == 0) ||
+ (found > 0 && interface->ifname[0] != 0))) {
+ print_queue(&pf->paltq->altq, 0,
+ &bwspec, interface->ifname[0] != 0,
+ opts);
+ if (nqueues && nqueues->tail) {
+ printf("{ ");
+ LOOP_THROUGH(struct node_queue,
+ queue, nqueues,
+ printf("%s ",
+ queue->queue);
+ );
+ printf("}");
+ }
+ printf("\n");
+ }
+ }
+ );
+ );
+
+ FREE_LIST(struct node_queue, nqueues);
+ FREE_LIST(struct node_if, interfaces);
+
+ if (!found) {
+ yyerror("queue %s has no parent", a->qname);
+ errs++;
+ }
+
+ if (errs)
+ return (1);
+ else
+ return (0);
+}
+
+static int
+pf_af_to_proto(sa_family_t af)
+{
+ if (af == AF_INET)
+ return (ETHERTYPE_IP);
+ if (af == AF_INET6)
+ return (ETHERTYPE_IPV6);
+
+ return (0);
+}
+
+void
+expand_eth_rule(struct pfctl_eth_rule *r,
+ struct node_if *interfaces, struct node_etherproto *protos,
+ struct node_mac *srcs, struct node_mac *dsts,
+ struct node_host *ipsrcs, struct node_host *ipdsts,
+ const char *bridge_to, const char *anchor_call)
+{
+ char tagname[PF_TAG_NAME_SIZE];
+ char match_tagname[PF_TAG_NAME_SIZE];
+ char qname[PF_QNAME_SIZE];
+
+ if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname))
+ errx(1, "%s: tagname", __func__);
+ if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >=
+ sizeof(match_tagname))
+ errx(1, "%s: match_tagname", __func__);
+ if (strlcpy(qname, r->qname, sizeof(qname)) >= sizeof(qname))
+ errx(1, "%s: qname", __func__);
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ LOOP_THROUGH(struct node_etherproto, proto, protos,
+ LOOP_THROUGH(struct node_mac, src, srcs,
+ LOOP_THROUGH(struct node_mac, dst, dsts,
+ LOOP_THROUGH(struct node_host, ipsrc, ipsrcs,
+ LOOP_THROUGH(struct node_host, ipdst, ipdsts,
+ strlcpy(r->ifname, interface->ifname,
+ sizeof(r->ifname));
+ r->ifnot = interface->not;
+ r->proto = proto->proto;
+ if (!r->proto && ipsrc->af)
+ r->proto = pf_af_to_proto(ipsrc->af);
+ else if (!r->proto && ipdst->af)
+ r->proto = pf_af_to_proto(ipdst->af);
+ bcopy(src->mac, r->src.addr, ETHER_ADDR_LEN);
+ bcopy(src->mask, r->src.mask, ETHER_ADDR_LEN);
+ r->src.neg = src->neg;
+ r->src.isset = src->isset;
+ r->ipsrc.addr = ipsrc->addr;
+ r->ipsrc.neg = ipsrc->not;
+ r->ipdst.addr = ipdst->addr;
+ r->ipdst.neg = ipdst->not;
+ bcopy(dst->mac, r->dst.addr, ETHER_ADDR_LEN);
+ bcopy(dst->mask, r->dst.mask, ETHER_ADDR_LEN);
+ r->dst.neg = dst->neg;
+ r->dst.isset = dst->isset;
+ r->nr = pf->eastack[pf->asd]->match++;
+
+ if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >=
+ sizeof(r->tagname))
+ errx(1, "%s: r->tagname", __func__);
+ if (strlcpy(r->match_tagname, match_tagname,
+ sizeof(r->match_tagname)) >= sizeof(r->match_tagname))
+ errx(1, "%s: r->match_tagname", __func__);
+ if (strlcpy(r->qname, qname, sizeof(r->qname)) >= sizeof(r->qname))
+ errx(1, "%s: r->qname", __func__);
+
+ if (bridge_to)
+ strlcpy(r->bridge_to, bridge_to, sizeof(r->bridge_to));
+
+ pfctl_append_eth_rule(pf, r, anchor_call);
+ ))))));
+
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_etherproto, protos);
+ FREE_LIST(struct node_mac, srcs);
+ FREE_LIST(struct node_mac, dsts);
+ FREE_LIST(struct node_host, ipsrcs);
+ FREE_LIST(struct node_host, ipdsts);
+}
+
+int
+apply_rdr_ports(struct pfctl_rule *r, struct pfctl_pool *rpool, struct redirspec *rs)
+{
+ if (rs == NULL)
+ return 0;
+
+ rpool->proxy_port[0] = ntohs(rs->rport.a);
+
+ if (!rs->rport.b && rs->rport.t) {
+ rpool->proxy_port[1] = ntohs(rs->rport.a) +
+ (ntohs(r->dst.port[1]) - ntohs(r->dst.port[0]));
+ } else {
+ if (validate_range(rs->rport.t, rs->rport.a,
+ rs->rport.b)) {
+ yyerror("invalid rdr-to port range");
+ return (1);
+ }
+ r->rdr.proxy_port[1] = ntohs(rs->rport.b);
+ }
+
+ if (rs->pool_opts.staticport) {
+ yyerror("the 'static-port' option is only valid with nat rules");
+ return 1;
+ }
+
+ if (rs->pool_opts.mape.offset) {
+ yyerror("the 'map-e-portset' option is only valid with nat rules");
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+apply_nat_ports(struct pfctl_pool *rpool, struct redirspec *rs)
+{
+ if (rs == NULL)
+ return 0;
+
+ rpool->proxy_port[0] = ntohs(rs->rport.a);
+ rpool->proxy_port[1] = ntohs(rs->rport.b);
+ if (!rpool->proxy_port[0] && !rpool->proxy_port[1]) {
+ rpool->proxy_port[0] = PF_NAT_PROXY_PORT_LOW;
+ rpool->proxy_port[1] = PF_NAT_PROXY_PORT_HIGH;
+ } else if (!rpool->proxy_port[1])
+ rpool->proxy_port[1] = rpool->proxy_port[0];
+
+ if (rs->pool_opts.staticport) {
+ if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW &&
+ rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) {
+ yyerror("the 'static-port' option can't"
+ " be used when specifying a port"
+ " range");
+ return 1;
+ }
+ rpool->proxy_port[0] = 0;
+ rpool->proxy_port[1] = 0;
+ }
+
+ if (rs->pool_opts.mape.offset) {
+ if (rs->pool_opts.staticport) {
+ yyerror("the 'map-e-portset' option"
+ " can't be used 'static-port'");
+ return 1;
+ }
+ if (rpool->proxy_port[0] != PF_NAT_PROXY_PORT_LOW &&
+ rpool->proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) {
+ yyerror("the 'map-e-portset' option"
+ " can't be used when specifying"
+ " a port range");
+ return 1;
+ }
+ rpool->mape = rs->pool_opts.mape;
+ }
+
+ return 0;
+}
+
+int
+apply_redirspec(struct pfctl_pool *rpool, struct redirspec *rs)
+{
+ struct node_host *h;
+ struct pfctl_pooladdr *pa;
+
+ if (rs == NULL)
+ return 0;
+
+ rpool->opts = rs->pool_opts.type;
+
+ if ((rpool->opts & PF_POOL_TYPEMASK) == PF_POOL_NONE &&
+ (rs->host->next != NULL ||
+ rs->host->addr.type == PF_ADDR_TABLE ||
+ DYNIF_MULTIADDR(rs->host->addr)))
+ rpool->opts = PF_POOL_ROUNDROBIN;
+
+ if (!PF_POOL_DYNTYPE(rpool->opts) &&
+ (disallow_table(rs->host, "tables are not supported by pool type") ||
+ disallow_alias(rs->host, "interface (%s) is not supported by pool type")))
+ return 1;
+
+ if (rs->host->next != NULL &&
+ ((rpool->opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN)) {
+ yyerror("r.route.opts must be PF_POOL_ROUNDROBIN");
+ return 1;
+ }
+
+ if (rs->host->next != NULL) {
+ if ((rpool->opts & PF_POOL_TYPEMASK) !=
+ PF_POOL_ROUNDROBIN) {
+ yyerror("only round-robin valid for multiple "
+ "redirection addresses");
+ return 1;
+ }
+ }
+
+ rpool->opts |= rs->pool_opts.opts;
+
+ if (rs->pool_opts.key != NULL)
+ memcpy(&(rpool->key), rs->pool_opts.key,
+ sizeof(struct pf_poolhashkey));
+
+ for (h = rs->host; h != NULL; h = h->next) {
+ pa = calloc(1, sizeof(struct pfctl_pooladdr));
+ if (pa == NULL)
+ err(1, "%s: calloc", __func__);
+ pa->addr = h->addr;
+ pa->af = h->af;
+ if (h->ifname != NULL) {
+ if (strlcpy(pa->ifname, h->ifname,
+ sizeof(pa->ifname)) >= sizeof(pa->ifname))
+ errx(1, "%s: strlcpy", __func__);
+ } else
+ pa->ifname[0] = 0;
+ TAILQ_INSERT_TAIL(&(rpool->list), pa, entries);
+ }
+
+ return 0;
+}
+
+int
+check_binat_redirspec(struct node_host *src_host, struct pfctl_rule *r,
+ sa_family_t af)
+{
+ struct pfctl_pooladdr *nat_pool = TAILQ_FIRST(&(r->nat.list));
+ int error = 0;
+
+ /* XXX: FreeBSD allows syntax like "{ host1 host2 }" for redirection
+ * pools but does not covert them to tables automatically, because
+ * syntax "{ (iface1 host1), (iface2 iface2) }" is allowed for route-to
+ * redirection. Add a FreeBSD-specific guard against using multiple
+ * hosts for source and redirection.
+ */
+ if (src_host->next) {
+ yyerror("invalid use of table as the source address "
+ "of a binat-to rule");
+ error++;
+ }
+ if (TAILQ_NEXT(nat_pool, entries)) {
+ yyerror ("tables cannot be used as the redirect "
+ "address of a binat-to rule");
+ error++;
+ }
+
+ if (disallow_table(src_host, "invalid use of table "
+ "<%s> as the source address of a binat-to rule") ||
+ disallow_alias(src_host, "invalid use of interface "
+ "(%s) as the source address of a binat-to rule")) {
+ error++;
+ } else if ((r->src.addr.type != PF_ADDR_ADDRMASK &&
+ r->src.addr.type != PF_ADDR_DYNIFTL) ||
+ (nat_pool->addr.type != PF_ADDR_ADDRMASK &&
+ nat_pool->addr.type != PF_ADDR_DYNIFTL)) {
+ yyerror("binat-to requires a specified "
+ "source and redirect address");
+ error++;
+ }
+ if (DYNIF_MULTIADDR(r->src.addr) ||
+ DYNIF_MULTIADDR(nat_pool->addr)) {
+ yyerror ("dynamic interfaces must be "
+ "used with:0 in a binat-to rule");
+ error++;
+ }
+ if (PF_AZERO(&r->src.addr.v.a.mask, af) ||
+ PF_AZERO(&(nat_pool->addr.v.a.mask), af)) {
+ yyerror ("source and redir addresses must have "
+ "a matching network mask in binat-rule");
+ error++;
+ }
+ if (nat_pool->addr.type == PF_ADDR_TABLE) {
+ yyerror ("tables cannot be used as the redirect "
+ "address of a binat-to rule");
+ error++;
+ }
+ if (r->direction != PF_INOUT) {
+ yyerror("binat-to cannot be specified "
+ "with a direction");
+ error++;
+ }
+
+ /* first specify outbound NAT rule */
+ r->direction = PF_OUT;
+
+ return (error);
+}
+
+void
+add_binat_rdr_rule(
+ struct pfctl_rule *binat_rule,
+ struct redirspec *binat_nat_redirspec, struct node_host *binat_src_host,
+ struct pfctl_rule *rdr_rule, struct redirspec **rdr_redirspec,
+ struct node_host **rdr_dst_host)
+{
+ struct node_host *rdr_src_host;
+
+ /*
+ * We're copying the whole rule, but we must re-init redir pools.
+ * FreeBSD uses lists of pfctl_pooladdr, we can't just overwrite them.
+ */
+ bcopy(binat_rule, rdr_rule, sizeof(struct pfctl_rule));
+ TAILQ_INIT(&(rdr_rule->rdr.list));
+ TAILQ_INIT(&(rdr_rule->nat.list));
+
+ /* now specify inbound rdr rule */
+ rdr_rule->direction = PF_IN;
+
+ if ((rdr_src_host = calloc(1, sizeof(*rdr_src_host))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(binat_src_host, rdr_src_host, sizeof(*rdr_src_host));
+ rdr_src_host->ifname = NULL;
+ rdr_src_host->next = NULL;
+ rdr_src_host->tail = NULL;
+
+ if (((*rdr_dst_host) = calloc(1, sizeof(**rdr_dst_host))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(&(binat_nat_redirspec->host->addr), &((*rdr_dst_host)->addr),
+ sizeof((*rdr_dst_host)->addr));
+ (*rdr_dst_host)->ifname = NULL;
+ (*rdr_dst_host)->next = NULL;
+ (*rdr_dst_host)->tail = NULL;
+
+ if (((*rdr_redirspec) = calloc(1, sizeof(**rdr_redirspec))) == NULL)
+ err(1, "%s", __func__);
+ bcopy(binat_nat_redirspec, (*rdr_redirspec), sizeof(**rdr_redirspec));
+ (*rdr_redirspec)->pool_opts.staticport = 0;
+ (*rdr_redirspec)->host = rdr_src_host;
+}
+
+void
+expand_rule(struct pfctl_rule *r, bool keeprule,
+ struct node_if *interfaces, struct redirspec *nat,
+ struct redirspec *rdr, struct redirspec *route,
+ struct node_proto *protos,
+ struct node_os *src_oses, struct node_host *src_hosts,
+ struct node_port *src_ports, struct node_host *dst_hosts,
+ struct node_port *dst_ports, struct node_uid *uids, struct node_gid *gids,
+ struct node_if *rcv, struct node_icmp *icmp_types)
+{
+ sa_family_t af = r->af;
+ int added = 0, error = 0;
+ char ifname[IF_NAMESIZE];
+ char label[PF_RULE_MAX_LABEL_COUNT][PF_RULE_LABEL_SIZE];
+ char tagname[PF_TAG_NAME_SIZE];
+ char match_tagname[PF_TAG_NAME_SIZE];
+ struct node_host *osrch, *odsth;
+ u_int8_t flags, flagset, keep_state;
+
+ memcpy(label, r->label, sizeof(r->label));
+ assert(sizeof(r->label) == sizeof(label));
+ if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >=
+ sizeof(match_tagname))
+ errx(1, "%s: strlcpy", __func__);
+ flags = r->flags;
+ flagset = r->flagset;
+ keep_state = r->keep_state;
+
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ LOOP_THROUGH(struct node_proto, proto, protos,
+ LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types,
+ LOOP_THROUGH(struct node_host, src_host, src_hosts,
+ LOOP_THROUGH(struct node_host, dst_host, dst_hosts,
+ LOOP_THROUGH(struct node_port, src_port, src_ports,
+ LOOP_THROUGH(struct node_port, dst_port, dst_ports,
+ LOOP_THROUGH(struct node_os, src_os, src_oses,
+ LOOP_THROUGH(struct node_uid, uid, uids,
+ LOOP_THROUGH(struct node_gid, gid, gids,
+
+ r->af = af;
+
+ if (r->rule_flag & PFRULE_AFTO) {
+ assert(nat != NULL);
+ r->naf = nat->af;
+ }
+
+ /* for link-local IPv6 address, interface must match up */
+ if ((r->af && src_host->af && r->af != src_host->af) ||
+ (r->af && dst_host->af && r->af != dst_host->af) ||
+ (src_host->af && dst_host->af &&
+ src_host->af != dst_host->af) ||
+ (src_host->ifindex && dst_host->ifindex &&
+ src_host->ifindex != dst_host->ifindex) ||
+ (src_host->ifindex && *interface->ifname &&
+ src_host->ifindex != ifa_nametoindex(interface->ifname)) ||
+ (dst_host->ifindex && *interface->ifname &&
+ dst_host->ifindex != ifa_nametoindex(interface->ifname)))
+ continue;
+ if (!r->af && src_host->af)
+ r->af = src_host->af;
+ else if (!r->af && dst_host->af)
+ r->af = dst_host->af;
+
+ if (*interface->ifname)
+ strlcpy(r->ifname, interface->ifname,
+ sizeof(r->ifname));
+ else if (ifa_indextoname(src_host->ifindex, ifname))
+ strlcpy(r->ifname, ifname, sizeof(r->ifname));
+ else if (ifa_indextoname(dst_host->ifindex, ifname))
+ strlcpy(r->ifname, ifname, sizeof(r->ifname));
+ else
+ memset(r->ifname, '\0', sizeof(r->ifname));
+
+ memcpy(r->label, label, sizeof(r->label));
+ if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >=
+ sizeof(r->tagname))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(r->match_tagname, match_tagname,
+ sizeof(r->match_tagname)) >= sizeof(r->match_tagname))
+ errx(1, "%s: strlcpy", __func__);
+
+ osrch = odsth = NULL;
+ if (src_host->addr.type == PF_ADDR_DYNIFTL) {
+ osrch = src_host;
+ if ((src_host = gen_dynnode(src_host, r->af)) == NULL)
+ err(1, "%s: calloc", __func__);
+ }
+ if (dst_host->addr.type == PF_ADDR_DYNIFTL) {
+ odsth = dst_host;
+ if ((dst_host = gen_dynnode(dst_host, r->af)) == NULL)
+ err(1, "%s: calloc", __func__);
+ }
+
+ error += check_netmask(src_host, r->af);
+ error += check_netmask(dst_host, r->af);
+
+ r->ifnot = interface->not;
+ r->proto = proto->proto;
+ r->src.addr = src_host->addr;
+ r->src.neg = src_host->not;
+ r->src.port[0] = src_port->port[0];
+ r->src.port[1] = src_port->port[1];
+ r->src.port_op = src_port->op;
+ r->dst.addr = dst_host->addr;
+ r->dst.neg = dst_host->not;
+ r->dst.port[0] = dst_port->port[0];
+ r->dst.port[1] = dst_port->port[1];
+ r->dst.port_op = dst_port->op;
+ r->uid.op = uid->op;
+ r->uid.uid[0] = uid->uid[0];
+ r->uid.uid[1] = uid->uid[1];
+ r->gid.op = gid->op;
+ r->gid.gid[0] = gid->gid[0];
+ r->gid.gid[1] = gid->gid[1];
+ if (rcv) {
+ strlcpy(r->rcv_ifname, rcv->ifname,
+ sizeof(r->rcv_ifname));
+ r->rcvifnot = rcv->not;
+ }
+ r->type = icmp_type->type;
+ r->code = icmp_type->code;
+
+ if ((keep_state == PF_STATE_MODULATE ||
+ keep_state == PF_STATE_SYNPROXY) &&
+ r->proto && r->proto != IPPROTO_TCP)
+ r->keep_state = PF_STATE_NORMAL;
+ else
+ r->keep_state = keep_state;
+
+ if (r->proto && r->proto != IPPROTO_TCP) {
+ r->flags = 0;
+ r->flagset = 0;
+ } else {
+ r->flags = flags;
+ r->flagset = flagset;
+ }
+ if (icmp_type->proto && r->proto != icmp_type->proto) {
+ yyerror("icmp-type mismatch");
+ error++;
+ }
+
+ if (src_os && src_os->os) {
+ r->os_fingerprint = pfctl_get_fingerprint(src_os->os);
+ if ((pf->opts & PF_OPT_VERBOSE2) &&
+ r->os_fingerprint == PF_OSFP_NOMATCH)
+ fprintf(stderr,
+ "warning: unknown '%s' OS fingerprint\n",
+ src_os->os);
+ } else {
+ r->os_fingerprint = PF_OSFP_ANY;
+ }
+
+ if (r->action == PF_RDR) {
+ /* Pre-FreeBSD 15 "rdr" rule */
+ error += apply_rdr_ports(r, &(r->rdr), rdr);
+ error += apply_redirspec(&(r->rdr), rdr);
+ } else if (r->action == PF_NAT) {
+ /* Pre-FreeBSD 15 "nat" rule */
+ error += apply_nat_ports(&(r->rdr), rdr);
+ error += apply_redirspec(&(r->rdr), rdr);
+ } else {
+ /* Modern rule with optional NAT, BINAT, RDR or ROUTE*/
+ error += apply_redirspec(&(r->route), route);
+
+ error += apply_nat_ports(&(r->nat), nat);
+ error += apply_redirspec(&(r->nat), nat);
+ error += apply_rdr_ports(r, &(r->rdr), rdr);
+ error += apply_redirspec(&(r->rdr), rdr);
+
+ if (nat && nat->binat)
+ error += check_binat_redirspec(src_host, r, af);
+ }
+
+ if (rule_consistent(r) < 0 || error)
+ yyerror("skipping rule due to errors");
+ else {
+ r->nr = pf->astack[pf->asd]->match++;
+ pfctl_append_rule(pf, r);
+ added++;
+ }
+
+ /* Generate binat's matching inbound rule */
+ if (!error && nat && nat->binat) {
+ struct pfctl_rule rdr_rule;
+ struct redirspec *rdr_redirspec;
+ struct node_host *rdr_dst_host;
+
+ add_binat_rdr_rule(
+ r, nat, src_hosts,
+ &rdr_rule, &rdr_redirspec, &rdr_dst_host);
+
+ expand_rule(&rdr_rule, true, interface, NULL, rdr_redirspec,
+ NULL, proto, src_os, dst_host, dst_port,
+ rdr_dst_host, src_port, uid, gid, rcv, icmp_type);
+ }
+
+ if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) {
+ free(src_host);
+ src_host = osrch;
+ }
+ if (odsth && dst_host->addr.type == PF_ADDR_DYNIFTL) {
+ free(dst_host);
+ dst_host = odsth;
+ }
+
+ ))))))))));
+
+ if (!keeprule) {
+ FREE_LIST(struct node_if, interfaces);
+ FREE_LIST(struct node_proto, protos);
+ FREE_LIST(struct node_host, src_hosts);
+ FREE_LIST(struct node_port, src_ports);
+ FREE_LIST(struct node_os, src_oses);
+ FREE_LIST(struct node_host, dst_hosts);
+ FREE_LIST(struct node_port, dst_ports);
+ FREE_LIST(struct node_uid, uids);
+ FREE_LIST(struct node_gid, gids);
+ FREE_LIST(struct node_icmp, icmp_types);
+ if (nat) {
+ FREE_LIST(struct node_host, nat->host);
+ free(nat);
+ }
+ if (rdr) {
+ FREE_LIST(struct node_host, rdr->host);
+ free(rdr);
+ }
+ if (route) {
+ FREE_LIST(struct node_host, route->host);
+ free(route);
+ }
+ }
+
+ if (!added)
+ yyerror("rule expands to no valid combination");
+}
+
+int
+expand_skip_interface(struct node_if *interfaces)
+{
+ int errs = 0;
+
+ if (!interfaces || (!interfaces->next && !interfaces->not &&
+ !strcmp(interfaces->ifname, "none"))) {
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set skip on none\n");
+ errs = pfctl_set_interface_flags(pf, "", PFI_IFLAG_SKIP, 0);
+ return (errs);
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set skip on {");
+ LOOP_THROUGH(struct node_if, interface, interfaces,
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" %s", interface->ifname);
+ if (interface->not) {
+ yyerror("skip on ! <interface> is not supported");
+ errs++;
+ } else
+ errs += pfctl_set_interface_flags(pf,
+ interface->ifname, PFI_IFLAG_SKIP, 1);
+ );
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" }\n");
+
+ FREE_LIST(struct node_if, interfaces);
+
+ if (errs)
+ return (1);
+ else
+ return (0);
+}
+
+void
+freehostlist(struct node_host *h)
+{
+ FREE_LIST(struct node_host, h);
+}
+
+#undef FREE_LIST
+#undef LOOP_THROUGH
+
+int
+check_rulestate(int desired_state)
+{
+ if (require_order && (rulestate > desired_state)) {
+ yyerror("Rules must be in order: options, ethernet, "
+ "normalization, queueing, translation, filtering");
+ return (1);
+ }
+ rulestate = desired_state;
+ return (0);
+}
+
+int
+kw_cmp(const void *k, const void *e)
+{
+ return (strcmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+lookup(char *s)
+{
+ /* this has to be sorted always */
+ static const struct keywords keywords[] = {
+ { "af-to", AFTO},
+ { "all", ALL},
+ { "allow-opts", ALLOWOPTS},
+ { "allow-related", ALLOW_RELATED},
+ { "altq", ALTQ},
+ { "anchor", ANCHOR},
+ { "antispoof", ANTISPOOF},
+ { "any", ANY},
+ { "bandwidth", BANDWIDTH},
+ { "binat", BINAT},
+ { "binat-anchor", BINATANCHOR},
+ { "binat-to", BINATTO},
+ { "bitmask", BITMASK},
+ { "block", BLOCK},
+ { "block-policy", BLOCKPOLICY},
+ { "bridge-to", BRIDGE_TO},
+ { "buckets", BUCKETS},
+ { "cbq", CBQ},
+ { "code", CODE},
+ { "codelq", CODEL},
+ { "debug", DEBUG},
+ { "divert-reply", DIVERTREPLY},
+ { "divert-to", DIVERTTO},
+ { "dnpipe", DNPIPE},
+ { "dnqueue", DNQUEUE},
+ { "drop", DROP},
+ { "dup-to", DUPTO},
+ { "endpoint-independent", ENDPI},
+ { "ether", ETHER},
+ { "fail-policy", FAILPOLICY},
+ { "fairq", FAIRQ},
+ { "fastroute", FASTROUTE},
+ { "file", FILENAME},
+ { "fingerprints", FINGERPRINTS},
+ { "flags", FLAGS},
+ { "floating", FLOATING},
+ { "flush", FLUSH},
+ { "for", FOR},
+ { "fragment", FRAGMENT},
+ { "from", FROM},
+ { "global", GLOBAL},
+ { "group", GROUP},
+ { "hfsc", HFSC},
+ { "hogs", HOGS},
+ { "hostid", HOSTID},
+ { "icmp-type", ICMPTYPE},
+ { "icmp6-type", ICMP6TYPE},
+ { "if-bound", IFBOUND},
+ { "in", IN},
+ { "include", INCLUDE},
+ { "inet", INET},
+ { "inet6", INET6},
+ { "interval", INTERVAL},
+ { "keep", KEEP},
+ { "keepcounters", KEEPCOUNTERS},
+ { "l3", L3},
+ { "label", LABEL},
+ { "limit", LIMIT},
+ { "linkshare", LINKSHARE},
+ { "load", LOAD},
+ { "log", LOG},
+ { "loginterface", LOGINTERFACE},
+ { "map-e-portset", MAPEPORTSET},
+ { "match", MATCH},
+ { "matches", MATCHES},
+ { "max", MAXIMUM},
+ { "max-mss", MAXMSS},
+ { "max-pkt-rate", MAXPKTRATE},
+ { "max-pkt-size", MAXPKTSIZE},
+ { "max-src-conn", MAXSRCCONN},
+ { "max-src-conn-rate", MAXSRCCONNRATE},
+ { "max-src-nodes", MAXSRCNODES},
+ { "max-src-states", MAXSRCSTATES},
+ { "min-ttl", MINTTL},
+ { "modulate", MODULATE},
+ { "nat", NAT},
+ { "nat-anchor", NATANCHOR},
+ { "nat-to", NATTO},
+ { "no", NO},
+ { "no-df", NODF},
+ { "no-route", NOROUTE},
+ { "no-sync", NOSYNC},
+ { "on", ON},
+ { "once", ONCE},
+ { "optimization", OPTIMIZATION},
+ { "os", OS},
+ { "out", OUT},
+ { "overload", OVERLOAD},
+ { "pass", PASS},
+ { "pflow", PFLOW},
+ { "port", PORT},
+ { "prefer-ipv6-nexthop", IPV6NH},
+ { "prio", PRIO},
+ { "priority", PRIORITY},
+ { "priq", PRIQ},
+ { "probability", PROBABILITY},
+ { "proto", PROTO},
+ { "qlimit", QLIMIT},
+ { "queue", QUEUE},
+ { "quick", QUICK},
+ { "random", RANDOM},
+ { "random-id", RANDOMID},
+ { "rdr", RDR},
+ { "rdr-anchor", RDRANCHOR},
+ { "rdr-to", RDRTO},
+ { "realtime", REALTIME},
+ { "reassemble", REASSEMBLE},
+ { "received-on", RECEIVEDON},
+ { "reply-to", REPLYTO},
+ { "require-order", REQUIREORDER},
+ { "return", RETURN},
+ { "return-icmp", RETURNICMP},
+ { "return-icmp6", RETURNICMP6},
+ { "return-rst", RETURNRST},
+ { "ridentifier", RIDENTIFIER},
+ { "round-robin", ROUNDROBIN},
+ { "route", ROUTE},
+ { "route-to", ROUTETO},
+ { "rtable", RTABLE},
+ { "rule", RULE},
+ { "ruleset-optimization", RULESET_OPTIMIZATION},
+ { "scrub", SCRUB},
+ { "set", SET},
+ { "set-tos", SETTOS},
+ { "skip", SKIP},
+ { "sloppy", SLOPPY},
+ { "source-hash", SOURCEHASH},
+ { "source-track", SOURCETRACK},
+ { "state", STATE},
+ { "state-defaults", STATEDEFAULTS},
+ { "state-policy", STATEPOLICY},
+ { "static-port", STATICPORT},
+ { "sticky-address", STICKYADDRESS},
+ { "syncookies", SYNCOOKIES},
+ { "synproxy", SYNPROXY},
+ { "table", TABLE},
+ { "tag", TAG},
+ { "tagged", TAGGED},
+ { "target", TARGET},
+ { "tbrsize", TBRSIZE},
+ { "timeout", TIMEOUT},
+ { "to", TO},
+ { "tos", TOS},
+ { "ttl", TTL},
+ { "upperlimit", UPPERLIMIT},
+ { "urpf-failed", URPFFAILED},
+ { "user", USER},
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
+ sizeof(keywords[0]), kw_cmp);
+
+ if (p) {
+ if (debug > 1)
+ fprintf(stderr, "%s: %d\n", s, p->k_val);
+ return (p->k_val);
+ } else {
+ if (debug > 1)
+ fprintf(stderr, "string: %s\n", s);
+ return (STRING);
+ }
+}
+
+#define START_EXPAND 1
+#define DONE_EXPAND 2
+
+static int expanding;
+
+int
+igetc(void)
+{
+ int c;
+ while (1) {
+ if (file->ungetpos > 0)
+ c = file->ungetbuf[--file->ungetpos];
+ else
+ c = getc(file->stream);
+ if (c == START_EXPAND)
+ expanding = 1;
+ else if (c == DONE_EXPAND)
+ expanding = 0;
+ else
+ break;
+ }
+ return (c);
+}
+
+int
+lgetc(int quotec)
+{
+ int c, next;
+
+ if (quotec) {
+ if ((c = igetc()) == EOF) {
+ yyerror("reached end of file while parsing quoted string");
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ return (quotec);
+ }
+ return (c);
+ }
+
+ while ((c = igetc()) == '\\') {
+ next = igetc();
+ if (next != '\n') {
+ c = next;
+ break;
+ }
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+
+ if (c == EOF) {
+ /*
+ * Fake EOL when hit EOF for the first time. This gets line
+ * count right if last line in included file is syntactically
+ * invalid and has no newline.
+ */
+ if (file->eof_reached == 0) {
+ file->eof_reached = 1;
+ return ('\n');
+ }
+ while (c == EOF) {
+ if (file == topfile || popfile() == EOF)
+ return (EOF);
+ c = igetc();
+ }
+ }
+ return (c);
+}
+
+void
+lungetc(int c)
+{
+ if (c == EOF)
+ return;
+ if (file->ungetpos >= file->ungetsize) {
+ void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
+ if (p == NULL)
+ err(1, "%s", __func__);
+ file->ungetbuf = p;
+ file->ungetsize *= 2;
+ }
+ file->ungetbuf[file->ungetpos++] = c;
+}
+
+int
+findeol(void)
+{
+ int c;
+
+ /* skip to either EOF or the first real EOL */
+ while (1) {
+ c = lgetc(0);
+ if (c == '\n') {
+ file->lineno++;
+ break;
+ }
+ if (c == EOF)
+ break;
+ }
+ return (ERROR);
+}
+
+int
+yylex(void)
+{
+ char buf[8096];
+ char *p, *val;
+ int quotec, next, c;
+ int token;
+
+top:
+ p = buf;
+ while ((c = lgetc(0)) == ' ' || c == '\t')
+ ; /* nothing */
+
+ yylval.lineno = file->lineno;
+ if (c == '#')
+ while ((c = lgetc(0)) != '\n' && c != EOF)
+ ; /* nothing */
+ if (c == '$' && !expanding) {
+ while (1) {
+ if ((c = lgetc(0)) == EOF)
+ return (0);
+
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ if (isalnum(c) || c == '_') {
+ *p++ = (char)c;
+ continue;
+ }
+ *p = '\0';
+ lungetc(c);
+ break;
+ }
+ val = symget(buf);
+ if (val == NULL) {
+ yyerror("macro '%s' not defined", buf);
+ return (findeol());
+ }
+ p = val + strlen(val) - 1;
+ lungetc(DONE_EXPAND);
+ while (p >= val) {
+ lungetc(*p);
+ p--;
+ }
+ lungetc(START_EXPAND);
+ goto top;
+ }
+
+ switch (c) {
+ case '\'':
+ case '"':
+ quotec = c;
+ while (1) {
+ if ((c = lgetc(quotec)) == EOF)
+ return (0);
+ if (c == '\n') {
+ file->lineno++;
+ continue;
+ } else if (c == '\\') {
+ if ((next = lgetc(quotec)) == EOF)
+ return (0);
+ if (next == quotec || next == ' ' ||
+ next == '\t')
+ c = next;
+ else if (next == '\n') {
+ file->lineno++;
+ continue;
+ }
+ else
+ lungetc(next);
+ } else if (c == quotec) {
+ *p = '\0';
+ break;
+ } else if (c == '\0') {
+ yyerror("syntax error");
+ return (findeol());
+ }
+ if (p + 1 >= buf + sizeof(buf) - 1) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ *p++ = (char)c;
+ }
+ yylval.v.string = strdup(buf);
+ if (yylval.v.string == NULL)
+ err(1, "%s: strdup", __func__);
+ return (STRING);
+ case '!':
+ next = lgetc(0);
+ if (next == '=')
+ return (NE);
+ lungetc(next);
+ break;
+ case '<':
+ next = lgetc(0);
+ if (next == '>') {
+ yylval.v.i = PF_OP_XRG;
+ return (PORTBINARY);
+ } else if (next == '=')
+ return (LE);
+ lungetc(next);
+ break;
+ case '>':
+ next = lgetc(0);
+ if (next == '<') {
+ yylval.v.i = PF_OP_IRG;
+ return (PORTBINARY);
+ } else if (next == '=')
+ return (GE);
+ lungetc(next);
+ break;
+ case '-':
+ next = lgetc(0);
+ if (next == '>')
+ return (ARROW);
+ lungetc(next);
+ break;
+ }
+
+#define allowed_to_end_number(x) \
+ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
+
+ if (c == '-' || isdigit(c)) {
+ do {
+ *p++ = c;
+ if ((size_t)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && isdigit(c));
+ lungetc(c);
+ if (p == buf + 1 && buf[0] == '-')
+ goto nodigits;
+ if (c == EOF || allowed_to_end_number(c)) {
+ const char *errstr = NULL;
+
+ *p = '\0';
+ yylval.v.number = strtonum(buf, LLONG_MIN,
+ LLONG_MAX, &errstr);
+ if (errstr) {
+ yyerror("\"%s\" invalid number: %s",
+ buf, errstr);
+ return (findeol());
+ }
+ return (NUMBER);
+ } else {
+nodigits:
+ while (p > buf + 1)
+ lungetc(*--p);
+ c = *--p;
+ if (c == '-')
+ return (c);
+ }
+ }
+
+#define allowed_in_string(x) \
+ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
+ x != '{' && x != '}' && x != '<' && x != '>' && \
+ x != '!' && x != '=' && x != '/' && x != '#' && \
+ x != ','))
+
+ if (isalnum(c) || c == ':' || c == '_') {
+ do {
+ *p++ = c;
+ if ((size_t)(p-buf) >= sizeof(buf)) {
+ yyerror("string too long");
+ return (findeol());
+ }
+ } while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
+ lungetc(c);
+ *p = '\0';
+ if ((token = lookup(buf)) == STRING)
+ if ((yylval.v.string = strdup(buf)) == NULL)
+ err(1, "%s: strdup", __func__);
+ return (token);
+ }
+ if (c == '\n') {
+ yylval.lineno = file->lineno;
+ file->lineno++;
+ }
+ if (c == EOF)
+ return (0);
+ return (c);
+}
+
+int
+check_file_secrecy(int fd, const char *fname)
+{
+ struct stat st;
+
+ if (fstat(fd, &st)) {
+ warn("cannot stat %s", fname);
+ return (-1);
+ }
+ if (st.st_uid != 0 && st.st_uid != getuid()) {
+ warnx("%s: owner not root or current user", fname);
+ return (-1);
+ }
+ if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
+ warnx("%s: group writable or world read/writable", fname);
+ return (-1);
+ }
+ return (0);
+}
+
+struct file *
+pushfile(const char *name, int secret)
+{
+ struct file *nfile;
+
+ if ((nfile = calloc(1, sizeof(struct file))) == NULL ||
+ (nfile->name = strdup(name)) == NULL) {
+ warn("%s", __func__);
+ if (nfile)
+ free(nfile);
+ return (NULL);
+ }
+ if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) {
+ nfile->stream = stdin;
+ free(nfile->name);
+ if ((nfile->name = strdup("stdin")) == NULL) {
+ warn("%s", __func__);
+ free(nfile);
+ return (NULL);
+ }
+ } else if ((nfile->stream = pfctl_fopen(nfile->name, "r")) == NULL) {
+ warn("%s: %s", __func__, nfile->name);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ } else if (secret &&
+ check_file_secrecy(fileno(nfile->stream), nfile->name)) {
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
+ nfile->ungetsize = 16;
+ nfile->ungetbuf = malloc(nfile->ungetsize);
+ if (nfile->ungetbuf == NULL) {
+ warn("%s", __func__);
+ fclose(nfile->stream);
+ free(nfile->name);
+ free(nfile);
+ return (NULL);
+ }
+ TAILQ_INSERT_TAIL(&files, nfile, entry);
+ return (nfile);
+}
+
+int
+popfile(void)
+{
+ struct file *prev;
+
+ if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
+ prev->errors += file->errors;
+
+ TAILQ_REMOVE(&files, file, entry);
+ fclose(file->stream);
+ free(file->name);
+ free(file->ungetbuf);
+ free(file);
+ file = prev;
+
+ return (file ? 0 : EOF);
+}
+
+int
+parse_config(char *filename, struct pfctl *xpf)
+{
+ int errors = 0;
+ struct sym *sym;
+
+ pf = xpf;
+ errors = 0;
+ rulestate = PFCTL_STATE_NONE;
+ returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT;
+ returnicmp6default =
+ (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT;
+ blockpolicy = PFRULE_DROP;
+ failpolicy = PFRULE_DROP;
+ require_order = 1;
+
+ if ((file = pushfile(filename, 0)) == NULL) {
+ warn("cannot open the main config file!");
+ return (-1);
+ }
+ topfile = file;
+
+ yyparse();
+ errors = file->errors;
+ popfile();
+
+ /* Free macros and check which have not been used. */
+ while ((sym = TAILQ_FIRST(&symhead))) {
+ if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used)
+ fprintf(stderr, "warning: macro '%s' not "
+ "used\n", sym->nam);
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+
+ return (errors ? -1 : 0);
+}
+
+int
+symset(const char *nam, const char *val, int persist)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0)
+ break;
+ }
+
+ if (sym != NULL) {
+ if (sym->persist == 1)
+ return (0);
+ else {
+ free(sym->nam);
+ free(sym->val);
+ TAILQ_REMOVE(&symhead, sym, entry);
+ free(sym);
+ }
+ }
+ if ((sym = calloc(1, sizeof(*sym))) == NULL)
+ return (-1);
+
+ sym->nam = strdup(nam);
+ if (sym->nam == NULL) {
+ free(sym);
+ return (-1);
+ }
+ sym->val = strdup(val);
+ if (sym->val == NULL) {
+ free(sym->nam);
+ free(sym);
+ return (-1);
+ }
+ sym->used = 0;
+ sym->persist = persist;
+ TAILQ_INSERT_TAIL(&symhead, sym, entry);
+ return (0);
+}
+
+int
+pfctl_cmdline_symset(char *s)
+{
+ char *sym, *val;
+ int ret;
+
+ if ((val = strrchr(s, '=')) == NULL)
+ return (-1);
+
+ sym = strndup(s, val - s);
+ if (sym == NULL)
+ err(1, "%s: malloc", __func__);
+
+ ret = symset(sym, val + 1, 1);
+ free(sym);
+
+ return (ret);
+}
+
+char *
+symget(const char *nam)
+{
+ struct sym *sym;
+
+ TAILQ_FOREACH(sym, &symhead, entry) {
+ if (strcmp(nam, sym->nam) == 0) {
+ sym->used = 1;
+ return (sym->val);
+ }
+ }
+ return (NULL);
+}
+
+void
+mv_rules(struct pfctl_ruleset *src, struct pfctl_ruleset *dst)
+{
+ int i;
+ struct pfctl_rule *r;
+
+ for (i = 0; i < PF_RULESET_MAX; ++i) {
+ TAILQ_FOREACH(r, src->rules[i].active.ptr, entries)
+ dst->anchor->match++;
+ TAILQ_CONCAT(dst->rules[i].active.ptr, src->rules[i].active.ptr, entries);
+ src->anchor->match = 0;
+ TAILQ_CONCAT(dst->rules[i].inactive.ptr, src->rules[i].inactive.ptr, entries);
+ }
+}
+
+void
+mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst)
+{
+ struct pfctl_eth_rule *r;
+
+ while ((r = TAILQ_FIRST(&src->rules)) != NULL) {
+ TAILQ_REMOVE(&src->rules, r, entries);
+ TAILQ_INSERT_TAIL(&dst->rules, r, entries);
+ dst->anchor->match++;
+ }
+ src->anchor->match = 0;
+}
+
+void
+mv_tables(struct pfctl *pf, struct pfr_ktablehead *ktables,
+ struct pfctl_anchor *a, struct pfctl_anchor *alast)
+{
+ struct pfr_ktable *kt, *kt_safe;
+ char new_path[PF_ANCHOR_MAXPATH];
+ char *path_cut;
+ int sz;
+ struct pfr_uktable *ukt;
+ SLIST_HEAD(, pfr_uktable) ukt_list;
+
+ /*
+ * Here we need to rename anchor path from temporal names such as
+ * _1/_2/foo to _1/bar/foo etc.
+ *
+ * This also means we need to remove and insert table to ktables
+ * tree as anchor path is being updated.
+ */
+ SLIST_INIT(&ukt_list);
+ DBGPRINT("%s [ %s ] (%s)\n", __func__, a->path, alast->path);
+ RB_FOREACH_SAFE(kt, pfr_ktablehead, ktables, kt_safe) {
+ path_cut = strstr(kt->pfrkt_anchor, alast->path);
+ if (path_cut != NULL) {
+ path_cut += strlen(alast->path);
+ if (*path_cut)
+ sz = snprintf(new_path, sizeof (new_path),
+ "%s%s", a->path, path_cut);
+ else
+ sz = snprintf(new_path, sizeof (new_path),
+ "%s", a->path);
+ if (sz >= sizeof (new_path))
+ errx(1, "new path is too long for %s@%s\n",
+ kt->pfrkt_name, kt->pfrkt_anchor);
+
+ DBGPRINT("%s %s@%s -> %s@%s\n", __func__,
+ kt->pfrkt_name, kt->pfrkt_anchor,
+ kt->pfrkt_name, new_path);
+ RB_REMOVE(pfr_ktablehead, ktables, kt);
+ strlcpy(kt->pfrkt_anchor, new_path,
+ sizeof(kt->pfrkt_anchor));
+ SLIST_INSERT_HEAD(&ukt_list, (struct pfr_uktable *)kt,
+ pfrukt_entry);
+ }
+ }
+
+ while ((ukt = SLIST_FIRST(&ukt_list)) != NULL) {
+ SLIST_REMOVE_HEAD(&ukt_list, pfrukt_entry);
+ if (RB_INSERT(pfr_ktablehead, ktables,
+ (struct pfr_ktable *)ukt) != NULL)
+ errx(1, "%s@%s exists already\n",
+ ukt->pfrukt_name,
+ ukt->pfrukt_anchor);
+ }
+}
+
+void
+decide_address_family(struct node_host *n, sa_family_t *af)
+{
+ if (*af != 0 || n == NULL)
+ return;
+ *af = n->af;
+ while ((n = n->next) != NULL) {
+ if (n->af != *af) {
+ *af = 0;
+ return;
+ }
+ }
+}
+
+void
+remove_invalid_hosts(struct node_host **nh, sa_family_t *af)
+{
+ struct node_host *n = *nh, *prev = NULL;
+
+ while (n != NULL) {
+ if (*af && n->af && n->af != *af) {
+ /* unlink and free n */
+ struct node_host *next = n->next;
+
+ /* adjust tail pointer */
+ if (n == (*nh)->tail)
+ (*nh)->tail = prev;
+ /* adjust previous node's next pointer */
+ if (prev == NULL)
+ *nh = next;
+ else
+ prev->next = next;
+ /* free node */
+ if (n->ifname != NULL)
+ free(n->ifname);
+ free(n);
+ n = next;
+ } else {
+ if (n->af && !*af)
+ *af = n->af;
+ prev = n;
+ n = n->next;
+ }
+ }
+}
+
+int
+invalid_redirect(struct node_host *nh, sa_family_t af)
+{
+ if (!af) {
+ struct node_host *n;
+
+ /* tables and dyniftl are ok without an address family */
+ for (n = nh; n != NULL; n = n->next) {
+ if (n->addr.type != PF_ADDR_TABLE &&
+ n->addr.type != PF_ADDR_DYNIFTL) {
+ yyerror("address family not given and "
+ "translation address expands to multiple "
+ "address families");
+ return (1);
+ }
+ }
+ }
+ if (nh == NULL) {
+ yyerror("no translation address with matching address family "
+ "found.");
+ return (1);
+ }
+ return (0);
+}
+
+int
+atoul(char *s, u_long *ulvalp)
+{
+ u_long ulval;
+ char *ep;
+
+ errno = 0;
+ ulval = strtoul(s, &ep, 0);
+ if (s[0] == '\0' || *ep != '\0')
+ return (-1);
+ if (errno == ERANGE && ulval == ULONG_MAX)
+ return (-1);
+ *ulvalp = ulval;
+ return (0);
+}
+
+int
+getservice(char *n)
+{
+ struct servent *s;
+ u_long ulval;
+
+ if (atoul(n, &ulval) == 0) {
+ if (ulval > 65535) {
+ yyerror("illegal port value %lu", ulval);
+ return (-1);
+ }
+ return (htons(ulval));
+ } else {
+ s = getservbyname(n, "tcp");
+ if (s == NULL)
+ s = getservbyname(n, "udp");
+ if (s == NULL)
+ s = getservbyname(n, "sctp");
+ if (s == NULL) {
+ yyerror("unknown port %s", n);
+ return (-1);
+ }
+ return (s->s_port);
+ }
+}
+
+int
+rule_label(struct pfctl_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT])
+{
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) {
+ if (s[i] == NULL)
+ return (0);
+
+ if (strlcpy(r->label[i], s[i], sizeof(r->label[0])) >=
+ sizeof(r->label[0])) {
+ yyerror("rule label too long (max %d chars)",
+ sizeof(r->label[0])-1);
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+int
+eth_rule_label(struct pfctl_eth_rule *r, char *s[PF_RULE_MAX_LABEL_COUNT])
+{
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++) {
+ if (s[i] == NULL)
+ return (0);
+
+ if (strlcpy(r->label[i], s[i], sizeof(r->label[0])) >=
+ sizeof(r->label[0])) {
+ yyerror("rule label too long (max %d chars)",
+ sizeof(r->label[0])-1);
+ return (-1);
+ }
+ }
+ return (0);
+}
+
+u_int16_t
+parseicmpspec(char *w, sa_family_t af)
+{
+ const struct icmpcodeent *p;
+ u_long ulval;
+ u_int8_t icmptype;
+
+ if (af == AF_INET)
+ icmptype = returnicmpdefault >> 8;
+ else
+ icmptype = returnicmp6default >> 8;
+
+ if (atoul(w, &ulval) == -1) {
+ if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) {
+ yyerror("unknown icmp code %s", w);
+ return (0);
+ }
+ ulval = p->code;
+ }
+ if (ulval > 255) {
+ yyerror("invalid icmp code %lu", ulval);
+ return (0);
+ }
+ return (icmptype << 8 | ulval);
+}
+
+int
+parseport(char *port, struct range *r, int extensions)
+{
+ char *p = strchr(port, ':');
+
+ if (p == NULL) {
+ if ((r->a = getservice(port)) == -1)
+ return (-1);
+ r->b = 0;
+ r->t = PF_OP_NONE;
+ return (0);
+ }
+ if ((extensions & PPORT_STAR) && !strcmp(p+1, "*")) {
+ *p = 0;
+ if ((r->a = getservice(port)) == -1)
+ return (-1);
+ r->b = 0;
+ r->t = PF_OP_IRG;
+ return (0);
+ }
+ if ((extensions & PPORT_RANGE)) {
+ *p++ = 0;
+ if ((r->a = getservice(port)) == -1 ||
+ (r->b = getservice(p)) == -1)
+ return (-1);
+ if (r->a == r->b) {
+ r->b = 0;
+ r->t = PF_OP_NONE;
+ } else
+ r->t = PF_OP_RRG;
+ return (0);
+ }
+ return (-1);
+}
+
+int
+pfctl_load_anchors(int dev, struct pfctl *pf)
+{
+ struct loadanchors *la;
+
+ TAILQ_FOREACH(la, &loadanchorshead, entries) {
+ if (pf->opts & PF_OPT_VERBOSE)
+ fprintf(stderr, "\nLoading anchor %s from %s\n",
+ la->anchorname, la->filename);
+ if (pfctl_rules(dev, la->filename, pf->opts, pf->optimize,
+ la->anchorname, pf->trans) == -1)
+ return (-1);
+ }
+
+ return (0);
+}
+
+int
+kw_casecmp(const void *k, const void *e)
+{
+ return (strcasecmp(k, ((const struct keywords *)e)->k_name));
+}
+
+int
+map_tos(char *s, int *val)
+{
+ /* DiffServ Codepoints and other TOS mappings */
+ const struct keywords toswords[] = {
+ { "af11", IPTOS_DSCP_AF11 },
+ { "af12", IPTOS_DSCP_AF12 },
+ { "af13", IPTOS_DSCP_AF13 },
+ { "af21", IPTOS_DSCP_AF21 },
+ { "af22", IPTOS_DSCP_AF22 },
+ { "af23", IPTOS_DSCP_AF23 },
+ { "af31", IPTOS_DSCP_AF31 },
+ { "af32", IPTOS_DSCP_AF32 },
+ { "af33", IPTOS_DSCP_AF33 },
+ { "af41", IPTOS_DSCP_AF41 },
+ { "af42", IPTOS_DSCP_AF42 },
+ { "af43", IPTOS_DSCP_AF43 },
+ { "critical", IPTOS_PREC_CRITIC_ECP },
+ { "cs0", IPTOS_DSCP_CS0 },
+ { "cs1", IPTOS_DSCP_CS1 },
+ { "cs2", IPTOS_DSCP_CS2 },
+ { "cs3", IPTOS_DSCP_CS3 },
+ { "cs4", IPTOS_DSCP_CS4 },
+ { "cs5", IPTOS_DSCP_CS5 },
+ { "cs6", IPTOS_DSCP_CS6 },
+ { "cs7", IPTOS_DSCP_CS7 },
+ { "ef", IPTOS_DSCP_EF },
+ { "inetcontrol", IPTOS_PREC_INTERNETCONTROL },
+ { "lowdelay", IPTOS_LOWDELAY },
+ { "netcontrol", IPTOS_PREC_NETCONTROL },
+ { "reliability", IPTOS_RELIABILITY },
+ { "throughput", IPTOS_THROUGHPUT },
+ { "va", IPTOS_DSCP_VA }
+ };
+ const struct keywords *p;
+
+ p = bsearch(s, toswords, sizeof(toswords)/sizeof(toswords[0]),
+ sizeof(toswords[0]), kw_casecmp);
+
+ if (p) {
+ *val = p->k_val;
+ return (1);
+ }
+ return (0);
+}
+
+int
+rt_tableid_max(void)
+{
+#ifdef __FreeBSD__
+ int fibs;
+ size_t l = sizeof(fibs);
+
+ if (sysctlbyname("net.fibs", &fibs, &l, NULL, 0) == -1)
+ fibs = 16; /* XXX RT_MAXFIBS, at least limit it some. */
+ /*
+ * As the OpenBSD code only compares > and not >= we need to adjust
+ * here given we only accept values of 0..n and want to avoid #ifdefs
+ * in the grammar.
+ */
+ return (fibs - 1);
+#else
+ return (RT_TABLEID_MAX);
+#endif
+}
+
+struct node_mac*
+node_mac_from_string(const char *str)
+{
+ struct node_mac *m;
+
+ m = calloc(1, sizeof(struct node_mac));
+ if (m == NULL)
+ err(1, "%s: calloc", __func__);
+
+ if (sscanf(str, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &m->mac[0], &m->mac[1], &m->mac[2], &m->mac[3], &m->mac[4],
+ &m->mac[5]) != 6) {
+ free(m);
+ yyerror("invalid MAC address");
+ return (NULL);
+ }
+
+ memset(m->mask, 0xff, ETHER_ADDR_LEN);
+ m->isset = true;
+ m->next = NULL;
+ m->tail = m;
+
+ return (m);
+}
+
+struct node_mac*
+node_mac_from_string_masklen(const char *str, int masklen)
+{
+ struct node_mac *m;
+
+ if (masklen < 0 || masklen > (ETHER_ADDR_LEN * 8)) {
+ yyerror("invalid MAC mask length");
+ return (NULL);
+ }
+
+ m = node_mac_from_string(str);
+ if (m == NULL)
+ return (NULL);
+
+ memset(m->mask, 0, ETHER_ADDR_LEN);
+ for (int i = 0; i < masklen; i++)
+ m->mask[i / 8] |= 1 << (i % 8);
+
+ return (m);
+}
+
+struct node_mac*
+node_mac_from_string_mask(const char *str, const char *mask)
+{
+ struct node_mac *m;
+
+ m = node_mac_from_string(str);
+ if (m == NULL)
+ return (NULL);
+
+ if (sscanf(mask, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
+ &m->mask[0], &m->mask[1], &m->mask[2], &m->mask[3], &m->mask[4],
+ &m->mask[5]) != 6) {
+ free(m);
+ yyerror("invalid MAC mask");
+ return (NULL);
+ }
+
+ return (m);
+}
+
+int
+filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts)
+{
+ if (opts->marker & FOM_ONCE) {
+ if ((r->action != PF_PASS && r->action != PF_DROP) || r->anchor) {
+ yyerror("'once' only applies to pass/block rules");
+ return (1);
+ }
+ r->rule_flag |= PFRULE_ONCE;
+ }
+
+ r->keep_state = opts->keep.action;
+ r->pktrate.limit = opts->pktrate.limit;
+ r->pktrate.seconds = opts->pktrate.seconds;
+ r->prob = opts->prob;
+ r->rtableid = opts->rtableid;
+ r->ridentifier = opts->ridentifier;
+ r->max_pkt_size = opts->max_pkt_size;
+ r->tos = opts->tos;
+
+ if (opts->nodf)
+ r->scrub_flags |= PFSTATE_NODF;
+ if (opts->randomid)
+ r->scrub_flags |= PFSTATE_RANDOMID;
+ if (opts->minttl)
+ r->min_ttl = opts->minttl;
+ if (opts->max_mss)
+ r->max_mss = opts->max_mss;
+
+ if (opts->tag)
+ if (strlcpy(r->tagname, opts->tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ return (1);
+ }
+ if (opts->match_tag)
+ if (strlcpy(r->match_tagname, opts->match_tag,
+ PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) {
+ yyerror("tag too long, max %u chars",
+ PF_TAG_NAME_SIZE - 1);
+ return (1);
+ }
+ r->match_tag_not = opts->match_tag_not;
+
+ if (rule_label(r, opts->label))
+ return (1);
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++)
+ free(opts->label[i]);
+
+ if (opts->marker & FOM_AFTO)
+ r->rule_flag |= PFRULE_AFTO;
+ if (opts->marker & FOM_SCRUB_TCP)
+ r->scrub_flags |= PFSTATE_SCRUB_TCP;
+ if (opts->marker & FOM_PRIO)
+ r->prio = opts->prio ? opts->prio : PF_PRIO_ZERO;
+ if (opts->marker & FOM_SETPRIO) {
+ r->set_prio[0] = opts->set_prio[0];
+ r->set_prio[1] = opts->set_prio[1];
+ r->scrub_flags |= PFSTATE_SETPRIO;
+ }
+ if (opts->marker & FOM_SETTOS) {
+ r->scrub_flags |= PFSTATE_SETTOS;
+ r->set_tos = opts->settos;
+ }
+
+ r->flags = opts->flags.b1;
+ r->flagset = opts->flags.b2;
+ if ((opts->flags.b1 & opts->flags.b2) != opts->flags.b1) {
+ yyerror("flags always false");
+ return (1);
+ }
+
+ if (opts->queues.qname != NULL) {
+ if (strlcpy(r->qname, opts->queues.qname,
+ sizeof(r->qname)) >= sizeof(r->qname)) {
+ yyerror("rule qname too long (max "
+ "%d chars)", sizeof(r->qname)-1);
+ return (1);
+ }
+ free(opts->queues.qname);
+ }
+ if (opts->queues.pqname != NULL) {
+ if (strlcpy(r->pqname, opts->queues.pqname,
+ sizeof(r->pqname)) >= sizeof(r->pqname)) {
+ yyerror("rule pqname too long (max "
+ "%d chars)", sizeof(r->pqname)-1);
+ return (1);
+ }
+ free(opts->queues.pqname);
+ }
+
+ if (opts->fragment)
+ r->rule_flag |= PFRULE_FRAGMENT;
+ r->allow_opts = opts->allowopts;
+
+ return (0);
+}
+
+static bool
+pfctl_setup_anchor(struct pfctl_rule *r, struct pfctl *pf, char *anchorname)
+{
+ char *p;
+
+ if (pf->astack[pf->asd + 1]) {
+ if (anchorname && strchr(anchorname, '/') != NULL) {
+ free(anchorname);
+ yyerror("anchor paths containing '/' "
+ "cannot be used for inline anchors.");
+ return (false);
+ }
+
+ /* Move inline rules into relative location. */
+ pfctl_anchor_setup(r,
+ &pf->astack[pf->asd]->ruleset,
+ anchorname ? anchorname : pf->alast->name);
+
+ if (r->anchor == NULL)
+ err(1, "anchorrule: unable to "
+ "create ruleset");
+
+ if (pf->alast != r->anchor) {
+ if (r->anchor->match) {
+ yyerror("inline anchor '%s' "
+ "already exists",
+ r->anchor->name);
+ return (false);
+ }
+ mv_rules(&pf->alast->ruleset,
+ &r->anchor->ruleset);
+ mv_tables(pf, &pfr_ktables, r->anchor, pf->alast);
+ }
+ pf_remove_if_empty_ruleset(&pf->alast->ruleset);
+ pf->alast = r->anchor;
+ } else {
+ if (! anchorname) {
+ yyerror("anchors without explicit "
+ "rules must specify a name");
+ return (false);
+ }
+ /*
+ * Don't make non-brace anchors part of the main anchor pool.
+ */
+ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) {
+ err(1, "anchorrule: calloc");
+ }
+ pf_init_ruleset(&r->anchor->ruleset);
+ r->anchor->ruleset.anchor = r->anchor;
+ if (strlcpy(r->anchor->path, anchorname,
+ sizeof(r->anchor->path)) >= sizeof(r->anchor->path)) {
+ errx(1, "anchorrule: strlcpy");
+ }
+ if ((p = strrchr(anchorname, '/')) != NULL) {
+ if (strlen(p) == 1) {
+ yyerror("anchorrule: bad anchor name %s",
+ anchorname);
+ return (false);
+ }
+ } else
+ p = anchorname;
+ if (strlcpy(r->anchor->name, p,
+ sizeof(r->anchor->name)) >= sizeof(r->anchor->name)) {
+ errx(1, "anchorrule: strlcpy");
+ }
+ }
+
+ return (true);
+}
diff --git a/sbin/pfctl/pf.os b/sbin/pfctl/pf.os
new file mode 100644
index 000000000000..e131d1b54756
--- /dev/null
+++ b/sbin/pfctl/pf.os
@@ -0,0 +1,708 @@
+# $OpenBSD: pf.os,v 1.27 2016/09/03 17:08:57 sthen Exp $
+# passive OS fingerprinting
+# -------------------------
+#
+# SYN signatures. Those signatures work for SYN packets only (duh!).
+#
+# (C) Copyright 2000-2003 by Michal Zalewski <lcamtuf@coredump.cx>
+# (C) Copyright 2003 by Mike Frantzen <frantzen@w4g.org>
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+#
+# This fingerprint database is adapted from Michal Zalewski's p0f passive
+# operating system package. The last database sync was from a Nov 3 2003
+# p0f.fp.
+#
+#
+# Each line in this file specifies a single fingerprint. Please read the
+# information below carefully before attempting to append any signatures
+# reported as UNKNOWN to this file to avoid mistakes.
+#
+# We use the following set metrics for fingerprinting:
+#
+# - Window size (WSS) - a highly OS dependent setting used for TCP/IP
+# performance control (max. amount of data to be sent without ACK).
+# Some systems use a fixed value for initial packets. On other
+# systems, it is a multiple of MSS or MTU (MSS+40). In some rare
+# cases, the value is just arbitrary.
+#
+# NEW SIGNATURE: if p0f reported a special value of 'Snn', the number
+# appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn'
+# means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the
+# value of nn is not fixed (unlikely), just copy the Snn or Tnn token
+# literally. If you know this device has a simple stack and a fixed
+# MTU, you can however multiply S value by MSS, or T value by MSS+40,
+# and put it instead of Snn or Tnn.
+#
+# If WSS otherwise looks like a fixed value (for example a multiple
+# of two), or if you can confirm the value is fixed, please quote
+# it literally. If there's no apparent pattern in WSS chosen, you
+# should consider wildcarding this value.
+#
+# - Overall packet size - a function of all IP and TCP options and bugs.
+#
+# NEW SIGNATURE: Copy this value literally.
+#
+# - Initial TTL - We check the actual TTL of a received packet. It can't
+# be higher than the initial TTL, and also shouldn't be dramatically
+# lower (maximum distance is defined as 40 hops).
+#
+# NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally.
+# You need to determine the initial TTL. The best way to do it is to
+# check the documentation for a remote system, or check its settings.
+# A fairly good method is to simply round the observed TTL up to
+# 32, 64, 128, or 255, but it should be noted that some obscure devices
+# might not use round TTLs (in particular, some shoddy appliances use
+# "original" initial TTL settings). If not sure, you can see how many
+# hops you're away from the remote party with traceroute or mtr.
+#
+# - Don't fragment flag (DF) - some modern OSes set this to implement PMTU
+# discovery. Others do not bother.
+#
+# NEW SIGNATURE: Copy this value literally.
+#
+# - Maximum segment size (MSS) - this setting is usually link-dependent. P0f
+# uses it to determine link type of the remote host.
+#
+# NEW SIGNATURE: Always wildcard this value, except for rare cases when
+# you have an appliance with a fixed value, know the system supports only
+# a very limited number of network interface types, or know the system
+# is using a value it pulled out of nowhere. Specific unique MSS
+# can be used to tell Google crawlbots from the rest of the population.
+#
+# - Window scaling (WSCALE) - this feature is used to scale WSS.
+# It extends the size of a TCP/IP window to 32 bits. Some modern
+# systems implement this feature.
+#
+# NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set
+# to zero or other low value. There's usually no need to wildcard this
+# parameter.
+#
+# - Timestamp - some systems that implement timestamps set them to
+# zero in the initial SYN. This case is detected and handled appropriately.
+#
+# - Selective ACK permitted - a flag set by systems that implement
+# selective ACK functionality.
+#
+# - The sequence of TCP all options (MSS, window scaling, selective ACK
+# permitted, timestamp, NOP). Other than the options previously
+# discussed, p0f also checks for timestamp option (a silly
+# extension to broadcast your uptime ;-), NOP options (used for
+# header padding) and sackOK option (selective ACK feature).
+#
+# NEW SIGNATURE: Copy the sequence literally.
+#
+# To wildcard any value (except for initial TTL or TCP options), replace
+# it with '*'. You can also use a modulo operator to match any values
+# that divide by nnn - '%nnn'.
+#
+# Fingerprint entry format:
+#
+# wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details
+#
+# wwww - window size (can be *, %nnn, Snn or Tnn). The special values
+# "S" and "T" which are a multiple of MSS or a multiple of MTU
+# respectively.
+# ttt - initial TTL
+# D - don't fragment bit (0 - not set, 1 - set)
+# ss - overall SYN packet size
+# OOO - option value and order specification (see below)
+# OS - OS genre (Linux, Solaris, Windows)
+# Version - OS Version (2.0.27 on x86, etc)
+# Subtype - OS subtype or patchlevel (SP3, lo0)
+# details - Generic OS details
+#
+# If OS genre starts with '*', p0f will not show distance, link type
+# and timestamp data. It is useful for userland TCP/IP stacks of
+# network scanners and so on, where many settings are randomized or
+# bogus.
+#
+# If OS genre starts with @, it denotes an approximate hit for a group
+# of operating systems (signature reporting still enabled in this case).
+# Use this feature at the end of this file to catch cases for which
+# you don't have a precise match, but can tell it's Windows or FreeBSD
+# or whatnot by looking at, say, flag layout alone.
+#
+# Option block description is a list of comma or space separated
+# options in the order they appear in the packet:
+#
+# N - NOP option
+# Wnnn - window scaling option, value nnn (or * or %nnn)
+# Mnnn - maximum segment size option, value nnn (or * or %nnn)
+# S - selective ACK OK
+# T - timestamp
+# T0 - timestamp with a zero value
+#
+# To denote no TCP options, use a single '.'.
+#
+# Please report any additions to this file, or any inaccuracies or
+# problems spotted, to the maintainers: lcamtuf@coredump.cx,
+# frantzen@openbsd.org and bugs@openbsd.org with a tcpdump packet
+# capture of the relevant SYN packet(s)
+#
+# A test and submission page is available at
+# http://lcamtuf.coredump.cx/p0f-help/
+#
+#
+# WARNING WARNING WARNING
+# -----------------------
+#
+# Do not add a system X as OS Y just because NMAP says so. It is often
+# the case that X is a NAT firewall. While nmap is talking to the
+# device itself, p0f is fingerprinting the guy behind the firewall
+# instead.
+#
+# When in doubt, use common sense, don't add something that looks like
+# a completely different system as Linux or FreeBSD or LinkSys router.
+# Check DNS name, establish a connection to the remote host and look
+# at SYN+ACK - does it look similar?
+#
+# Some users tweak their TCP/IP settings - enable or disable RFC1323
+# functionality, enable or disable timestamps or selective ACK,
+# disable PMTU discovery, change MTU and so on. Always compare a new rule
+# to other fingerprints for this system, and verify the system isn't
+# "customized" before adding it. It is OK to add signature variants
+# caused by a commonly used software (personal firewalls, security
+# packages, etc), but it makes no sense to try to add every single
+# possible /proc/sys/net/ipv4 tweak on Linux or so.
+#
+# KEEP IN MIND: Some packet firewalls configured to normalize outgoing
+# traffic (OpenBSD pf with "scrub" enabled, for example) will, well,
+# normalize packets. Signatures will not correspond to the originating
+# system (and probably not quite to the firewall either).
+#
+# NOTE: Try to keep this file in some reasonable order, from most to
+# least likely systems. This will speed up operation. Also keep most
+# generic and broad rules near the end.
+#
+
+##########################
+# Standard OS signatures #
+##########################
+
+# ----------------- AIX ---------------------
+
+# AIX is first because its signatures are close to NetBSD, MacOS X and
+# Linux 2.0, but it uses a fairly rare MSSes, at least sometimes...
+# This is a shoddy hack, though.
+
+45046:64:0:44:M*: AIX:4.3::AIX 4.3
+16384:64:0:44:M512: AIX:4.3:2-3:AIX 4.3.2 and earlier
+
+16384:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2
+16384:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2
+32768:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T: AIX:4.3:3:AIX 4.3.3-5.2
+65535:64:0:60:M512,N,W%2,N,N,T: AIX:5.1-5.2::AIX 4.3.3-5.2
+65535:64:0:64:M*,N,W1,N,N,T,N,N,S: AIX:5.3:ML1:AIX 5.3 ML1
+
+# ----------------- Linux -------------------
+
+# S1:64:0:44:M*:A: Linux:1.2::Linux 1.2.x (XXX quirks support)
+512:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x
+16384:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x
+
+# Endian snafu! Nelson says "ha-ha":
+2:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+64:64:0:44:M*: Linux:2.0:3x:Linux 2.0.3x (MkLinux) on Mac
+
+
+S4:64:1:60:M1360,S,T,N,W0: Linux:google::Linux (Google crawlbot)
+
+S2:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4 (big boy)
+S3:64:1:60:M*,S,T,N,W0: Linux:2.4:.18-21:Linux 2.4.18 and newer
+S4:64:1:60:M*,S,T,N,W0: Linux:2.4::Linux 2.4/2.6 <= 2.6.7
+S4:64:1:60:M*,S,T,N,W0: Linux:2.6:.1-7:Linux 2.4/2.6 <= 2.6.7
+
+S4:64:1:60:M*,S,T,N,W5: Linux:2.6::Linux 2.6 (newer, 1)
+S4:64:1:60:M*,S,T,N,W6: Linux:2.6::Linux 2.6 (newer, 2)
+S4:64:1:60:M*,S,T,N,W7: Linux:2.6::Linux 2.6 (newer, 3)
+T4:64:1:60:M*,S,T,N,W7: Linux:2.6::Linux 2.6 (newer, 4)
+
+S10:64:1:60:M*,S,T,N,W4: Linux:3.0::Linux 3.0
+
+S3:64:1:60:M*,S,T,N,W1: Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W1: Linux:2.5-2.6::Linux 2.5/2.6
+S3:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4)
+S4:64:1:60:M*,S,T,N,W2: Linux:2.5::Linux 2.5 (sometimes 2.4)
+
+S20:64:1:60:M*,S,T,N,W0: Linux:2.2:20-25:Linux 2.2.20 and newer
+S22:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2
+S11:64:1:60:M*,S,T,N,W0: Linux:2.2::Linux 2.2
+
+# Popular cluster config scripts disable timestamps and
+# selective ACK:
+S4:64:1:48:M1460,N,W0: Linux:2.4:cluster:Linux 2.4 in cluster
+
+# This needs to be investigated. On some systems, WSS
+# is selected as a multiple of MTU instead of MSS. I got
+# many submissions for this for many late versions of 2.4:
+T4:64:1:60:M1412,S,T,N,W0: Linux:2.4::Linux 2.4 (late, uncommon)
+
+# This happens only over loopback, but let's make folks happy:
+32767:64:1:60:M16396,S,T,N,W0: Linux:2.4:lo0:Linux 2.4 (local)
+S8:64:1:60:M3884,S,T,N,W0: Linux:2.2:lo0:Linux 2.2 (local)
+
+# Opera visitors:
+16384:64:1:60:M*,S,T,N,W0: Linux:2.2:Opera:Linux 2.2 (Opera?)
+32767:64:1:60:M*,S,T,N,W0: Linux:2.4:Opera:Linux 2.4 (Opera?)
+
+# Some fairly common mods:
+S4:64:1:52:M*,N,N,S,N,W0: Linux:2.4:ts:Linux 2.4 w/o timestamps
+S22:64:1:52:M*,N,N,S,N,W0: Linux:2.2:ts:Linux 2.2 w/o timestamps
+
+
+# ----------------- FreeBSD -----------------
+
+16384:64:1:44:M*: FreeBSD:2.0-2.2::FreeBSD 2.0-4.2
+16384:64:1:44:M*: FreeBSD:3.0-3.5::FreeBSD 2.0-4.2
+16384:64:1:44:M*: FreeBSD:4.0-4.2::FreeBSD 2.0-4.2
+16384:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4
+
+1024:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.4::FreeBSD 4.4
+
+57344:64:1:44:M*: FreeBSD:4.6-4.8:noRFC1323:FreeBSD 4.6-4.8 (no RFC1323)
+57344:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.6-4.9::FreeBSD 4.6-4.9
+
+32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.1 (or MacOS X)
+32768:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.1::FreeBSD 4.8-5.1 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:4.8-4.11::FreeBSD 4.8-5.2 (or MacOS X)
+65535:64:1:60:M*,N,W0,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.8-5.2 (or MacOS X)
+65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:4.7-4.11::FreeBSD 4.7-5.2
+65535:64:1:60:M*,N,W1,N,N,T: FreeBSD:5.0-5.2::FreeBSD 4.7-5.2
+
+# XXX need quirks support
+# 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (1)
+# 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (2)
+# 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1-5.4::5.1-current (3)
+# 65535:64:1:44:M*:Z:FreeBSD:5.2::FreeBSD 5.2 (no RFC1323)
+
+# 16384:64:1:60:M*,N,N,N,N,N,N,T:FreeBSD:4.4:noTS:FreeBSD 4.4 (w/o timestamps)
+
+# ----------------- NetBSD ------------------
+
+16384:64:0:60:M*,N,W0,N,N,T: NetBSD:1.3::NetBSD 1.3
+65535:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6:opera:NetBSD 1.6 (Opera)
+16384:64:0:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6
+16384:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:df:NetBSD 1.6 (DF)
+65535:64:1:60:M*,N,W1,N,N,T0: NetBSD:1.6::NetBSD 1.6W-current (DF)
+65535:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6::NetBSD 1.6X (DF)
+32768:64:1:60:M*,N,W0,N,N,T0: NetBSD:1.6:randomization:NetBSD 1.6ZH-current (w/ ip_id randomization)
+
+# ----------------- OpenBSD -----------------
+
+16384:64:0:60:M*,N,W0,N,N,T: OpenBSD:2.6::NetBSD 1.3 (or OpenBSD 2.6)
+16384:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8::OpenBSD 3.0-4.8
+16384:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.8:no-df:OpenBSD 3.0-4.8 (scrub no-df)
+57344:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0::OpenBSD 3.3-4.0
+57344:64:0:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.3-4.0:no-df:OpenBSD 3.3-4.0 (scrub no-df)
+
+65535:64:1:64:M*,N,N,S,N,W0,N,N,T: OpenBSD:3.0-4.0:opera:OpenBSD 3.0-4.0 (Opera)
+
+16384:64:1:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9::OpenBSD 4.9
+16384:64:0:64:M*,N,N,S,N,W3,N,N,T: OpenBSD:4.9:no-df:OpenBSD 4.9 (scrub no-df)
+
+16384:64:1:64:M*,N,N,S,N,W6,N,N,T: OpenBSD:6.1::OpenBSD 6.1
+16384:64:0:64:M*,N,N,S,N,W6,N,N,T: OpenBSD:6.1:no-df:OpenBSD 6.1 (scrub no-df)
+
+# ----------------- DragonFly BSD -----------------
+
+57344:64:1:60:M*,N,W0,N,N,T: DragonFly:1.0:A:DragonFly 1.0A
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T: DragonFly:1.2-1.12::DragonFly 1.2-1.12
+5840:64:1:60:M*,S,T,N,W4: DragonFly:2.0-2.1::DragonFly 2.0-2.1
+57344:64:0:64:M*,N,W0,N,N,S,N,N,T: DragonFly:2.2-2.3::DragonFly 2.2-2.3
+57344:64:0:64:M*,N,W5,N,N,S,N,N,T: DragonFly:2.4-2.7::DragonFly 2.4-2.7
+
+# ----------------- Solaris -----------------
+
+S17:64:1:64:N,W3,N,N,T0,N,N,S,M*: Solaris:8:RFC1323:Solaris 8 RFC1323
+S17:64:1:48:N,N,S,M*: Solaris:8::Solaris 8
+S17:255:1:44:M*: Solaris:2.5-2.7::Solaris 2.5 to 7
+
+S6:255:1:44:M*: Solaris:2.6-2.7::Solaris 2.6 to 7
+S23:255:1:44:M*: Solaris:2.5:1:Solaris 2.5.1
+S34:64:1:48:M*,N,N,S: Solaris:2.9::Solaris 9
+S44:255:1:44:M*: Solaris:2.7::Solaris 7
+
+4096:64:0:44:M1460: SunOS:4.1::SunOS 4.1.x
+
+S34:64:1:52:M*,N,W0,N,N,S: Solaris:10:beta:Solaris 10 (beta)
+32850:64:1:64:M*,N,N,T,N,W1,N,N,S: Solaris:10::Solaris 10 1203
+
+# ----------------- IRIX --------------------
+
+49152:64:0:44:M*: IRIX:6.4::IRIX 6.4
+61440:64:0:44:M*: IRIX:6.2-6.5::IRIX 6.2-6.5
+49152:64:0:52:M*,N,W2,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+49152:64:0:52:M*,N,W3,N,N,S: IRIX:6.5:RFC1323:IRIX 6.5 (RFC1323)
+
+61440:64:0:48:M*,N,N,S: IRIX:6.5:12-21:IRIX 6.5.12 - 6.5.21
+49152:64:0:48:M*,N,N,S: IRIX:6.5:15-21:IRIX 6.5.15 - 6.5.21
+
+49152:60:0:64:M*,N,W2,N,N,T,N,N,S: IRIX:6.5:IP27:IRIX 6.5 IP27
+
+
+# ----------------- Tru64 -------------------
+
+32768:64:1:48:M*,N,W0: Tru64:4.0::Tru64 4.0 (or OS/2 Warp 4)
+32768:64:0:48:M*,N,W0: Tru64:5.0::Tru64 5.0
+8192:64:0:44:M1460: Tru64:5.1:noRFC1323:Tru64 6.1 (no RFC1323) (or QNX 6)
+61440:64:0:48:M*,N,W0: Tru64:5.1a:JP4:Tru64 v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack)
+
+# ----------------- OpenVMS -----------------
+
+6144:64:1:60:M*,N,W0,N,N,T: OpenVMS:7.2::OpenVMS 7.2 (Multinet 4.4 stack)
+
+# ----------------- MacOS -------------------
+
+# XXX Need EOL tcp opt support
+# S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic
+
+# XXX some of these use EOL too
+16616:255:1:48:M*,W0: MacOS:7.3-7.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,W0: MacOS:8.0-8.6:OTTCP:MacOS 7.3-8.6 (OTTCP)
+16616:255:1:48:M*,N,N,N: MacOS:8.1-8.6:OTTCP:MacOS 8.1-8.6 (OTTCP)
+32768:255:1:48:M*,W0,N: MacOS:9.0-9.2::MacOS 9.0-9.2
+65535:255:1:48:M*,N,N,N,N: MacOS:9.1::MacOS 9.1 (OT 2.7.4)
+
+
+# ----------------- Windows -----------------
+
+# Windows TCP/IP stack is a mess. For most recent XP, 2000 and
+# even 98, the patchlevel, not the actual OS version, is more
+# relevant to the signature. They share the same code, so it would
+# seem. Luckily for us, almost all Windows 9x boxes have an
+# awkward MSS of 536, which I use to tell one from another
+# in most difficult cases.
+
+8192:32:1:44:M*: Windows:3.11::Windows 3.11 (Tucows)
+S44:64:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95::Windows 95
+8192:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:95:b:Windows 95b
+
+# There were so many tweaking tools and so many stack versions for
+# Windows 98 it is no longer possible to tell them from each other
+# without some very serious research. Until then, there's an insane
+# number of signatures, for your amusement:
+
+S44:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL)
+8192:32:1:48:M*,N,N,S: Windows:98:lowTTL:Windows 98 (low TTL)
+%8192:64:1:48:M536,N,N,S: Windows:98::Windows 98
+%8192:128:1:48:M536,N,N,S: Windows:98::Windows 98
+S4:64:1:48:M*,N,N,S: Windows:98::Windows 98
+S6:64:1:48:M*,N,N,S: Windows:98::Windows 98
+S12:64:1:48:M*,N,N,S: Windows:98::Windows 98
+T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98
+32767:64:1:48:M*,N,N,S: Windows:98::Windows 98
+37300:64:1:48:M*,N,N,S: Windows:98::Windows 98
+46080:64:1:52:M*,N,W3,N,N,S: Windows:98:RFC1323:Windows 98 (RFC1323)
+65535:64:1:44:M*: Windows:98:noSack:Windows 98 (no sack)
+S16:128:1:48:M*,N,N,S: Windows:98::Windows 98
+S16:128:1:64:M*,N,W0,N,N,T0,N,N,S: Windows:98::Windows 98
+S26:128:1:48:M*,N,N,S: Windows:98::Windows 98
+T30:128:1:48:M*,N,N,S: Windows:98::Windows 98
+32767:128:1:52:M*,N,W0,N,N,S: Windows:98::Windows 98
+60352:128:1:48:M*,N,N,S: Windows:98::Windows 98
+60352:128:1:64:M*,N,W2,N,N,T0,N,N,S: Windows:98::Windows 98
+
+# What's with 1414 on NT?
+T31:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a
+64512:128:1:44:M1414: Windows:NT:4.0:Windows NT 4.0 SP6a
+8192:128:1:44:M*: Windows:NT:4.0:Windows NT 4.0 (older)
+
+# Windows XP and 2000. Most of the signatures that were
+# either dubious or non-specific (no service pack data)
+# were deleted and replaced with generics at the end.
+
+65535:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP1
+65535:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP1
+%8192:128:1:48:M*,N,N,S: Windows:2000:SP2+:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+%8192:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP2, XP SP1 (seldom 98 4.10.2222)
+S20:128:1:48:M*,N,N,S: Windows:2000::Windows 2000/XP SP3
+S20:128:1:48:M*,N,N,S: Windows:XP:SP3:Windows 2000/XP SP3
+S45:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4, XP SP 1
+S45:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows 2000 SP4, XP SP 1
+40320:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows 2000 SP4
+
+S6:128:1:48:M*,N,N,S: Windows:2000:SP2:Windows XP, 2000 SP2+
+S6:128:1:48:M*,N,N,S: Windows:XP::Windows XP, 2000 SP2+
+S12:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows XP SP1
+S44:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows Pro SP1, 2000 SP3
+S44:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows Pro SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S: Windows:2000:SP3:Windows SP1, 2000 SP3
+64512:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP3
+32767:128:1:48:M*,N,N,S: Windows:2000:SP4:Windows SP1, 2000 SP4
+32767:128:1:48:M*,N,N,S: Windows:XP:SP1:Windows SP1, 2000 SP4
+
+8192:128:1:52:M*,N,W2,N,N,S: Windows:Vista::Windows Vista/7
+
+# Odds, ends, mods:
+
+S52:128:1:48:M1260,N,N,S: Windows:2000:cisco:Windows XP/2000 via Cisco
+S52:128:1:48:M1260,N,N,S: Windows:XP:cisco:Windows XP/2000 via Cisco
+65520:128:1:48:M*,N,N,S: Windows:XP::Windows XP bare-bone
+16384:128:1:52:M536,N,W0,N,N,S: Windows:2000:ZoneAlarm:Windows 2000 w/ZoneAlarm?
+2048:255:0:40:.: Windows:.NET::Windows .NET Enterprise Server
+
+44620:64:0:48:M*,N,N,S: Windows:ME::Windows ME no SP (?)
+S6:255:1:48:M536,N,N,S: Windows:95:winsock2:Windows 95 winsock 2
+32768:32:1:52:M1460,N,W0,N,N,S: Windows:2003:AS:Windows 2003 AS
+
+
+# No need to be more specific, it passes:
+# *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) XXX quirk
+# there is an equiv similar generic sig w/o the quirk
+
+# ----------------- HP/UX -------------------
+
+32768:64:1:44:M*: HP-UX:B.10.20::HP-UX B.10.20
+32768:64:0:48:M*,W0,N: HP-UX:11.0::HP-UX 11.0
+32768:64:1:48:M*,W0,N: HP-UX:11.10::HP-UX 11.0 or 11.11
+32768:64:1:48:M*,W0,N: HP-UX:11.11::HP-UX 11.0 or 11.11
+
+# Whoa. Hardcore WSS.
+0:64:0:48:M*,W0,N: HP-UX:B.11.00:A:HP-UX B.11.00 A (RFC1323)
+
+# ----------------- RiscOS ------------------
+
+# We don't yet support the ?12 TCP option
+#16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12: RISCOS:3.70-4.36::RISC OS 3.70-4.36
+12288:32:0:44:M536: RISC OS:3.70:4.10:RISC OS 3.70 inet 4.10
+
+# XXX quirk
+# 4096:64:1:56:M1460,N,N,T:T: RISC OS:3.70:freenet:RISC OS 3.70 freenet 2.00
+
+
+
+# ----------------- BSD/OS ------------------
+
+# Once again, power of two WSS is also shared by MacOS X with DF set
+8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:3.1::BSD/OS 3.1-4.3 (or MacOS X 10.2 w/DF)
+8192:64:1:60:M1460,N,W0,N,N,T: BSD/OS:4.0-4.3::BSD/OS 3.1-4.3 (or MacOS X 10.2)
+
+
+# ---------------- NewtonOS -----------------
+
+4096:64:0:44:M1420: NewtonOS:2.1::NewtonOS 2.1
+
+# ---------------- NeXTSTEP -----------------
+
+S4:64:0:44:M1024: NeXTSTEP:3.3::NeXTSTEP 3.3
+S8:64:0:44:M512: NeXTSTEP:3.3::NeXTSTEP 3.3
+
+# ------------------ BeOS -------------------
+
+1024:255:0:48:M*,N,W0: BeOS:5.0-5.1::BeOS 5.0-5.1
+12288:255:0:44:M1402: BeOS:5.0::BeOS 5.0.x
+
+# ------------------ OS/400 -----------------
+
+8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR4::OS/400 VR4/R5
+8192:64:1:60:M1440,N,W0,N,N,T: OS/400:VR5::OS/400 VR4/R5
+4096:64:1:60:M1440,N,W0,N,N,T: OS/400:V4R5:CF67032:OS/400 V4R5 + CF67032
+
+# XXX quirk
+# 28672:64:0:44:M1460:A:OS/390:?
+
+# ------------------ ULTRIX -----------------
+
+16384:64:0:40:.: ULTRIX:4.5::ULTRIX 4.5
+
+# ------------------- QNX -------------------
+
+S16:64:0:44:M512: QNX:::QNX demodisk
+
+# ------------------ Novell -----------------
+
+16384:128:1:44:M1460: Novell:NetWare:5.0:Novel Netware 5.0
+6144:128:1:44:M1460: Novell:IntranetWare:4.11:Novell IntranetWare 4.11
+6144:128:1:44:M1368: Novell:BorderManager::Novell BorderManager ?
+
+6144:128:1:52:M*,W0,N,S,N,N: Novell:Netware:6:Novell Netware 6 SP3
+
+
+# ----------------- SCO ------------------
+S3:64:1:60:M1460,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1
+S17:64:1:60:M1380,N,W0,N,N,T: SCO:UnixWare:7.1:SCO UnixWare 7.1.3 MP3
+S23:64:1:44:M1380: SCO:OpenServer:5.0:SCO OpenServer 5.0
+
+# ------------------- DOS -------------------
+
+2048:255:0:44:M536: DOS:WATTCP:1.05:DOS Arachne via WATTCP/1.05
+T2:255:0:44:M984: DOS:WATTCP:1.05Arachne:Arachne via WATTCP/1.05 (eepro)
+
+# ------------------ OS/2 -------------------
+
+S56:64:0:44:M512: OS/2:4::OS/2 4
+28672:64:0:44:M1460: OS/2:4::OS/2 Warp 4.0
+
+# ----------------- TOPS-20 -----------------
+
+# Another hardcore MSS, one of the ACK leakers hunted down.
+# XXX QUIRK 0:64:0:44:M1460:A:TOPS-20:version 7
+0:64:0:44:M1460: TOPS-20:7::TOPS-20 version 7
+
+# ----------------- FreeMiNT ----------------
+
+S44:255:0:44:M536: FreeMiNT:1:16A:FreeMiNT 1 patch 16A (Atari)
+
+# ------------------ AMIGA ------------------
+
+# XXX TCP option 12
+# S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack
+
+# ------------------ Plan9 ------------------
+
+65535:255:0:48:M1460,W0,N: Plan9:4::Plan9 edition 4
+
+# ----------------- AMIGAOS -----------------
+
+16384:64:1:48:M1560,N,N,S: AMIGAOS:3.9::AMIGAOS 3.9 BB2 MiamiDX
+
+###########################################
+# Appliance / embedded / other signatures #
+###########################################
+
+# ---------- Firewalls / routers ------------
+
+S12:64:1:44:M1460: @Checkpoint:::Checkpoint (unknown 1)
+S12:64:1:48:N,N,S,M1460: @Checkpoint:::Checkpoint (unknown 2)
+4096:32:0:44:M1460: ExtremeWare:4.x::ExtremeWare 4.x
+
+# XXX TCP option 12
+# S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3
+# S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026
+
+S4:64:1:60:W0,N,S,T,M1460: FortiNet:FortiGate:50:FortiNet FortiGate 50
+
+8192:64:1:44:M1460: Eagle:::Eagle Secure Gateway
+
+S52:128:1:48:M1260,N,N,N,N: LinkSys:WRV54G::LinkSys WRV54G VPN router
+
+
+
+# ------- Switches and other stuff ----------
+
+4128:255:0:44:M*: Cisco:::Cisco Catalyst 3500, 7500 etc
+S8:255:0:44:M*: Cisco:12008::Cisco 12008
+60352:128:1:64:M1460,N,W2,N,N,T,N,N,S: Alteon:ACEswitch::Alteon ACEswitch
+64512:128:1:44:M1370: Nortel:Contivity Client::Nortel Conectivity Client
+
+
+# ---------- Caches and whatnots ------------
+
+S4:64:1:52:M1460,N,N,S,N,W0: AOL:web cache::AOL web cache
+
+32850:64:1:64:N,W1,N,N,T,N,N,S,M*: NetApp:5.x::NetApp Data OnTap 5.x
+16384:64:1:64:M1460,N,N,S,N,W0,N: NetApp:5.3:1:NetApp 5.3.1
+65535:64:0:64:M1460,N,N,S,N,W*,N,N,T: NetApp:5.3-5.5::NetApp 5.3-5.5
+65535:64:0:60:M1460,N,W0,N,N,T: NetApp:CacheFlow::NetApp CacheFlow
+8192:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:5.2:1:NetApp NetCache 5.2.1
+20480:64:1:64:M1460,N,N,S,N,W0,N,N,T: NetApp:4.1::NetApp NetCache4.1
+
+65535:64:0:60:M1460,N,W0,N,N,T: CacheFlow:4.1::CacheFlow CacheOS 4.1
+8192:64:0:60:M1380,N,N,N,N,N,N,T: CacheFlow:1.1::CacheFlow CacheOS 1.1
+
+S4:64:0:48:M1460,N,N,S: Cisco:Content Engine::Cisco Content Engine
+
+27085:128:0:40:.: Dell:PowerApp cache::Dell PowerApp (Linux-based)
+
+65535:255:1:48:N,W1,M1460: Inktomi:crawler::Inktomi crawler
+S1:255:1:60:M1460,S,T,N,W0: LookSmart:ZyBorg::LookSmart ZyBorg
+
+16384:255:0:40:.: Proxyblocker:::Proxyblocker (what's this?)
+
+65535:255:0:48:M*,N,N,S: Redline:::Redline T|X 2200
+
+32696:128:0:40:M1460: Spirent:Avalanche::Spirent Web Avalanche HTTP benchmarking engine
+
+# ----------- Embedded systems --------------
+
+S9:255:0:44:M536: PalmOS:Tungsten:C:PalmOS Tungsten C
+S5:255:0:44:M536: PalmOS:3::PalmOS 3/4
+S5:255:0:44:M536: PalmOS:4::PalmOS 3/4
+S4:255:0:44:M536: PalmOS:3:5:PalmOS 3.5
+2948:255:0:44:M536: PalmOS:3:5:PalmOS 3.5.3 (Handera)
+S29:255:0:44:M536: PalmOS:5::PalmOS 5.0
+16384:255:0:44:M1398: PalmOS:5.2:Clie:PalmOS 5.2 (Clie)
+S14:255:0:44:M1350: PalmOS:5.2:Treo:PalmOS 5.2.1 (Treo)
+
+S23:64:1:64:N,W1,N,N,T,N,N,S,M1460: SymbianOS:7::SymbianOS 7
+
+8192:255:0:44:M1460: SymbianOS:6048::Symbian OS 6048 (Nokia 7650?)
+8192:255:0:44:M536: SymbianOS:9210::Symbian OS (Nokia 9210?)
+S22:64:1:56:M1460,T,S: SymbianOS:P800::Symbian OS ? (SE P800?)
+S36:64:1:56:M1360,T,S: SymbianOS:6600::Symbian OS 60xx (Nokia 6600?)
+
+
+# Perhaps S4?
+5840:64:1:60:M1452,S,T,N,W1: Zaurus:3.10::Zaurus 3.10
+
+32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S: PocketPC:2002::PocketPC 2002
+
+S1:255:0:44:M346: Contiki:1.1:rc0:Contiki 1.1-rc0
+
+4096:128:0:44:M1460: Sega:Dreamcast:3.0:Sega Dreamcast Dreamkey 3.0
+T5:64:0:44:M536: Sega:Dreamcast:HKT-3020:Sega Dreamcast HKT-3020 (browser disc 51027)
+S22:64:1:44:M1460: Sony:PS2::Sony Playstation 2 (SOCOM?)
+
+S12:64:0:44:M1452: AXIS:5600:v5.64:AXIS Printer Server 5600 v5.64
+
+3100:32:1:44:M1460: Windows:CE:2.0:Windows CE 2.0
+
+####################
+# Fancy signatures #
+####################
+
+1024:64:0:40:.: *NMAP:syn scan:1:NMAP syn scan (1)
+2048:64:0:40:.: *NMAP:syn scan:2:NMAP syn scan (2)
+3072:64:0:40:.: *NMAP:syn scan:3:NMAP syn scan (3)
+4096:64:0:40:.: *NMAP:syn scan:4:NMAP syn scan (4)
+
+# Requires quirks support
+# 1024:64:0:40:.:A:*NMAP:TCP sweep probe (1)
+# 2048:64:0:40:.:A:*NMAP:TCP sweep probe (2)
+# 3072:64:0:40:.:A:*NMAP:TCP sweep probe (3)
+# 4096:64:0:40:.:A:*NMAP:TCP sweep probe (4)
+
+1024:64:0:60:W10,N,M265,T: *NMAP:OS:1:NMAP OS detection probe (1)
+2048:64:0:60:W10,N,M265,T: *NMAP:OS:2:NMAP OS detection probe (2)
+3072:64:0:60:W10,N,M265,T: *NMAP:OS:3:NMAP OS detection probe (3)
+4096:64:0:60:W10,N,M265,T: *NMAP:OS:4:NMAP OS detection probe (4)
+
+32767:64:0:40:.: *NAST:::NASTsyn scan
+
+# Requires quirks support
+# 12345:255:0:40:.:A:-p0f:sendsyn utility
+
+
+#####################################
+# Generic signatures - just in case #
+#####################################
+
+#*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:4.0-4.9::FreeBSD 4.x/5.x
+#*:64:1:60:M*,N,W*,N,N,T: @FreeBSD:5.0-5.1::FreeBSD 4.x/5.x
+
+*:128:1:52:M*,N,W0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W*,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:52:M*,N,W*,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323 no tstamp)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W0,N,N,T0,N,N,S: @Windows:2000:RFC1323:Windows XP/2000 (RFC1323)
+*:128:1:64:M*,N,W*,N,N,T0,N,N,S: @Windows:XP:RFC1323:Windows XP (RFC1323, w+)
+*:128:1:48:M536,N,N,S: @Windows:98::Windows 98
+*:128:1:48:M*,N,N,S: @Windows:XP::Windows XP/2000
+*:128:1:48:M*,N,N,S: @Windows:2000::Windows XP/2000
+
+
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
new file mode 100644
index 000000000000..417ff70de975
--- /dev/null
+++ b/sbin/pfctl/pf_print_state.c
@@ -0,0 +1,461 @@
+/* $OpenBSD: pf_print_state.c,v 1.52 2008/08/12 16:40:18 david Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/endian.h>
+#include <net/if.h>
+#define TCPSTATES
+#include <netinet/tcp_fsm.h>
+#include <netinet/sctp.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void print_name(struct pf_addr *, sa_family_t);
+
+void
+print_addr(struct pf_addr_wrap *addr, sa_family_t af, int verbose)
+{
+ switch (addr->type) {
+ case PF_ADDR_DYNIFTL:
+ printf("(%s", addr->v.ifname);
+ if (addr->iflags & PFI_AFLAG_NETWORK)
+ printf(":network");
+ if (addr->iflags & PFI_AFLAG_BROADCAST)
+ printf(":broadcast");
+ if (addr->iflags & PFI_AFLAG_PEER)
+ printf(":peer");
+ if (addr->iflags & PFI_AFLAG_NOALIAS)
+ printf(":0");
+ if (verbose) {
+ if (addr->p.dyncnt <= 0)
+ printf(":*");
+ else
+ printf(":%d", addr->p.dyncnt);
+ }
+ printf(")");
+ break;
+ case PF_ADDR_TABLE:
+ if (verbose)
+ if (addr->p.tblcnt == -1)
+ printf("<%s:*>", addr->v.tblname);
+ else
+ printf("<%s:%d>", addr->v.tblname,
+ addr->p.tblcnt);
+ else
+ printf("<%s>", addr->v.tblname);
+ return;
+ case PF_ADDR_RANGE: {
+ print_addr_str(af, &addr->v.a.addr);
+ printf(" - ");
+ print_addr_str(af, &addr->v.a.mask);
+
+ break;
+ }
+ case PF_ADDR_ADDRMASK:
+ if (PF_AZERO(&addr->v.a.addr, AF_INET6) &&
+ PF_AZERO(&addr->v.a.mask, AF_INET6))
+ printf("any");
+ else
+ print_addr_str(af, &addr->v.a.addr);
+ break;
+ case PF_ADDR_NOROUTE:
+ printf("no-route");
+ return;
+ case PF_ADDR_URPFFAILED:
+ printf("urpf-failed");
+ return;
+ default:
+ printf("?");
+ return;
+ }
+
+ /* mask if not _both_ address and mask are zero */
+ if (addr->type != PF_ADDR_RANGE &&
+ !(PF_AZERO(&addr->v.a.addr, AF_INET6) &&
+ PF_AZERO(&addr->v.a.mask, AF_INET6))) {
+ if (af == AF_INET || af == AF_INET6) {
+ int bits = unmask(&addr->v.a.mask);
+ if (bits < (af == AF_INET ? 32 : 128))
+ printf("/%d", bits);
+ }
+ }
+}
+
+void
+print_addr_str(sa_family_t af, struct pf_addr *addr)
+{
+ static char buf[48];
+
+ if (inet_ntop(af, addr, buf, sizeof(buf)) == NULL)
+ printf("?");
+ else
+ printf("%s", buf);
+}
+
+void
+print_name(struct pf_addr *addr, sa_family_t af)
+{
+ struct sockaddr_storage ss;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ char host[NI_MAXHOST];
+
+ memset(&ss, 0, sizeof(ss));
+ ss.ss_family = af;
+ if (ss.ss_family == AF_INET) {
+ sin = (struct sockaddr_in *)&ss;
+ sin->sin_len = sizeof(*sin);
+ sin->sin_addr = addr->v4;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&ss;
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_addr = addr->v6;
+ }
+
+ if (getnameinfo((struct sockaddr *)&ss, ss.ss_len, host, sizeof(host),
+ NULL, 0, NI_NOFQDN) != 0)
+ printf("?");
+ else
+ printf("%s", host);
+}
+
+void
+print_host(struct pf_addr *addr, u_int16_t port, sa_family_t af, int opts)
+{
+ struct pf_addr_wrap aw;
+
+ if (opts & PF_OPT_USEDNS)
+ print_name(addr, af);
+ else {
+ memset(&aw, 0, sizeof(aw));
+ aw.v.a.addr = *addr;
+ memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask));
+ print_addr(&aw, af, opts & PF_OPT_VERBOSE2);
+ }
+
+ if (port) {
+ if (af == AF_INET)
+ printf(":%u", ntohs(port));
+ else
+ printf("[%u]", ntohs(port));
+ }
+}
+
+void
+print_seq(struct pfctl_state_peer *p)
+{
+ if (p->seqdiff)
+ printf("[%u + %u](+%u)", p->seqlo,
+ p->seqhi - p->seqlo, p->seqdiff);
+ else
+ printf("[%u + %u]", p->seqlo,
+ p->seqhi - p->seqlo);
+}
+
+
+static const char *
+sctp_state_name(int state)
+{
+ switch (state) {
+ case SCTP_CLOSED:
+ return ("CLOSED");
+ case SCTP_BOUND:
+ return ("BOUND");
+ case SCTP_LISTEN:
+ return ("LISTEN");
+ case SCTP_COOKIE_WAIT:
+ return ("COOKIE_WAIT");
+ case SCTP_COOKIE_ECHOED:
+ return ("COOKIE_ECHOED");
+ case SCTP_ESTABLISHED:
+ return ("ESTABLISHED");
+ case SCTP_SHUTDOWN_SENT:
+ return ("SHUTDOWN_SENT");
+ case SCTP_SHUTDOWN_RECEIVED:
+ return ("SHUTDOWN_RECEIVED");
+ case SCTP_SHUTDOWN_ACK_SENT:
+ return ("SHUTDOWN_ACK_SENT");
+ case SCTP_SHUTDOWN_PENDING:
+ return ("SHUTDOWN_PENDING");
+ default:
+ return ("?");
+ }
+}
+
+void
+print_state(struct pfctl_state *s, int opts)
+{
+ struct pfctl_state_peer *src, *dst;
+ struct pfctl_state_key *key, *sk, *nk;
+ const char *protoname;
+ int min, sec;
+ uint8_t proto;
+ int afto = (s->key[PF_SK_STACK].af != s->key[PF_SK_WIRE].af);
+ int idx;
+ const char *sn_type_names[] = PF_SN_TYPE_NAMES;
+#ifndef __NO_STRICT_ALIGNMENT
+ struct pfctl_state_key aligned_key[2];
+
+ bcopy(&s->key, aligned_key, sizeof(aligned_key));
+ key = aligned_key;
+#else
+ key = s->key;
+#endif
+
+ proto = s->key[PF_SK_WIRE].proto;
+
+ if (s->direction == PF_OUT) {
+ src = &s->src;
+ dst = &s->dst;
+ sk = &key[PF_SK_STACK];
+ nk = &key[PF_SK_WIRE];
+ if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6)
+ sk->port[0] = nk->port[0];
+ } else {
+ src = &s->dst;
+ dst = &s->src;
+ sk = &key[PF_SK_WIRE];
+ nk = &key[PF_SK_STACK];
+ if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6)
+ sk->port[1] = nk->port[1];
+ }
+ printf("%s ", s->ifname);
+ if ((protoname = pfctl_proto2name(proto)) != NULL)
+ printf("%s ", protoname);
+ else
+ printf("%u ", proto);
+
+ print_host(&nk->addr[1], nk->port[1], nk->af, opts);
+ if (nk->af != sk->af || PF_ANEQ(&nk->addr[1], &sk->addr[1], nk->af) ||
+ nk->port[1] != sk->port[1]) {
+ idx = afto ? 0 : 1;
+ printf(" (");
+ print_host(&sk->addr[idx], sk->port[idx], sk->af,
+ opts);
+ printf(")");
+ }
+ if (s->direction == PF_OUT || (afto && s->direction == PF_IN))
+ printf(" -> ");
+ else
+ printf(" <- ");
+ print_host(&nk->addr[0], nk->port[0], nk->af, opts);
+ if (nk->af != sk->af || PF_ANEQ(&nk->addr[0], &sk->addr[0], nk->af) ||
+ nk->port[0] != sk->port[0]) {
+ idx = afto ? 1 : 0;
+ printf(" (");
+ print_host(&sk->addr[idx], sk->port[idx], sk->af,
+ opts);
+ printf(")");
+ }
+
+ printf(" ");
+ if (proto == IPPROTO_TCP) {
+ if (src->state <= TCPS_TIME_WAIT &&
+ dst->state <= TCPS_TIME_WAIT)
+ printf(" %s:%s\n", tcpstates[src->state],
+ tcpstates[dst->state]);
+ else if (src->state == PF_TCPS_PROXY_SRC ||
+ dst->state == PF_TCPS_PROXY_SRC)
+ printf(" PROXY:SRC\n");
+ else if (src->state == PF_TCPS_PROXY_DST ||
+ dst->state == PF_TCPS_PROXY_DST)
+ printf(" PROXY:DST\n");
+ else
+ printf(" <BAD STATE LEVELS %u:%u>\n",
+ src->state, dst->state);
+ if (opts & PF_OPT_VERBOSE) {
+ printf(" ");
+ print_seq(src);
+ if (src->wscale && dst->wscale)
+ printf(" wscale %u",
+ src->wscale & PF_WSCALE_MASK);
+ printf(" ");
+ print_seq(dst);
+ if (src->wscale && dst->wscale)
+ printf(" wscale %u",
+ dst->wscale & PF_WSCALE_MASK);
+ printf("\n");
+ }
+ } else if (proto == IPPROTO_UDP && src->state < PFUDPS_NSTATES &&
+ dst->state < PFUDPS_NSTATES) {
+ const char *states[] = PFUDPS_NAMES;
+
+ printf(" %s:%s\n", states[src->state], states[dst->state]);
+ } else if (proto == IPPROTO_SCTP) {
+ printf(" %s:%s\n", sctp_state_name(src->state),
+ sctp_state_name(dst->state));
+#ifndef INET6
+ } else if (proto != IPPROTO_ICMP && src->state < PFOTHERS_NSTATES &&
+ dst->state < PFOTHERS_NSTATES) {
+#else
+ } else if (proto != IPPROTO_ICMP && proto != IPPROTO_ICMPV6 &&
+ src->state < PFOTHERS_NSTATES && dst->state < PFOTHERS_NSTATES) {
+#endif
+ /* XXX ICMP doesn't really have state levels */
+ const char *states[] = PFOTHERS_NAMES;
+
+ printf(" %s:%s\n", states[src->state], states[dst->state]);
+ } else {
+ printf(" %u:%u\n", src->state, dst->state);
+ }
+
+ if (opts & PF_OPT_VERBOSE) {
+ u_int32_t creation = s->creation;
+ u_int32_t expire = s->expire;
+
+ sec = creation % 60;
+ creation /= 60;
+ min = creation % 60;
+ creation /= 60;
+ printf(" age %.2u:%.2u:%.2u", creation, min, sec);
+ sec = expire % 60;
+ expire /= 60;
+ min = expire % 60;
+ expire /= 60;
+ printf(", expires in %.2u:%.2u:%.2u", expire, min, sec);
+
+ printf(", %ju:%ju pkts, %ju:%ju bytes",
+ s->packets[0],
+ s->packets[1],
+ s->bytes[0],
+ s->bytes[1]);
+ if (s->anchor != -1)
+ printf(", anchor %u", s->anchor);
+ if (s->rule != -1)
+ printf(", rule %u", s->rule);
+ if (s->state_flags & PFSTATE_ALLOWOPTS)
+ printf(", allow-opts");
+ if (s->state_flags & PFSTATE_SLOPPY)
+ printf(", sloppy");
+ if (s->state_flags & PFSTATE_NOSYNC)
+ printf(", no-sync");
+ if (s->state_flags & PFSTATE_PFLOW)
+ printf(", pflow");
+ if (s->state_flags & PFSTATE_ACK)
+ printf(", psync-ack");
+ if (s->state_flags & PFSTATE_NODF)
+ printf(", no-df");
+ if (s->state_flags & PFSTATE_SETTOS)
+ printf(", set-tos 0x%2.2x", s->set_tos);
+ if (s->state_flags & PFSTATE_RANDOMID)
+ printf(", random-id");
+ if (s->state_flags & PFSTATE_SCRUB_TCP)
+ printf(", reassemble-tcp");
+ if (s->state_flags & PFSTATE_SETPRIO)
+ printf(", set-prio (0x%02x 0x%02x)",
+ s->set_prio[0], s->set_prio[1]);
+ if (s->dnpipe || s->dnrpipe) {
+ if (s->state_flags & PFSTATE_DN_IS_PIPE)
+ printf(", dummynet pipe (%d %d)",
+ s->dnpipe, s->dnrpipe);
+ if (s->state_flags & PFSTATE_DN_IS_QUEUE)
+ printf(", dummynet queue (%d %d)",
+ s->dnpipe, s->dnrpipe);
+ }
+ if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT)
+ printf(", %s", sn_type_names[PF_SN_LIMIT]);
+ if (s->src_node_flags & PFSTATE_SRC_NODE_LIMIT_GLOBAL)
+ printf(" global");
+ if (s->src_node_flags & PFSTATE_SRC_NODE_NAT)
+ printf(", %s", sn_type_names[PF_SN_NAT]);
+ if (s->src_node_flags & PFSTATE_SRC_NODE_ROUTE)
+ printf(", %s", sn_type_names[PF_SN_ROUTE]);
+ if (s->log)
+ printf(", log");
+ if (s->log & PF_LOG_ALL)
+ printf(" (all)");
+ if (s->min_ttl)
+ printf(", min-ttl %d", s->min_ttl);
+ if (s->max_mss)
+ printf(", max-mss %d", s->max_mss);
+ printf("\n");
+ }
+ if (opts & PF_OPT_VERBOSE2) {
+ u_int64_t id;
+
+ bcopy(&s->id, &id, sizeof(u_int64_t));
+ printf(" id: %016jx creatorid: %08x", id, s->creatorid);
+ if (s->rt) {
+ switch (s->rt) {
+ case PF_ROUTETO:
+ printf(" route-to: ");
+ break;
+ case PF_DUPTO:
+ printf(" dup-to: ");
+ break;
+ case PF_REPLYTO:
+ printf(" reply-to: ");
+ break;
+ default:
+ printf(" gateway: ");
+ }
+ print_host(&s->rt_addr, 0, s->rt_af, opts);
+ if (s->rt_ifname[0])
+ printf("@%s", s->rt_ifname);
+ }
+ if (s->rtableid != -1)
+ printf(" rtable: %d", s->rtableid);
+ printf("\n");
+
+ if (strcmp(s->ifname, s->orig_ifname) != 0)
+ printf(" origif: %s\n", s->orig_ifname);
+ }
+}
+
+int
+unmask(struct pf_addr *m)
+{
+ int i = 31, j = 0, b = 0;
+ u_int32_t tmp;
+
+ while (j < 4 && m->addr32[j] == 0xffffffff) {
+ b += 32;
+ j++;
+ }
+ if (j < 4) {
+ tmp = ntohl(m->addr32[j]);
+ for (i = 31; tmp & (1 << i); --i)
+ b++;
+ }
+ return (b);
+}
diff --git a/sbin/pfctl/pf_ruleset.c b/sbin/pfctl/pf_ruleset.c
new file mode 100644
index 000000000000..2b7ec09f28aa
--- /dev/null
+++ b/sbin/pfctl/pf_ruleset.c
@@ -0,0 +1,539 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * Copyright (c) 2002,2003 Henning Brauer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Effort sponsored in part by the Defense Advanced Research Projects
+ * Agency (DARPA) and Air Force Research Laboratory, Air Force
+ * Materiel Command, USAF, under agreement number F30602-01-2-0537.
+ *
+ * $OpenBSD: pf_ruleset.c,v 1.2 2008/12/18 15:31:37 dhill Exp $
+ */
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/mbuf.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+
+#include <net/if.h>
+#include <net/vnet.h>
+#include <net/pfvar.h>
+
+#ifdef INET6
+#include <netinet/ip6.h>
+#endif /* INET6 */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#define rs_malloc(x) calloc(1, x)
+#define rs_free(x) free(x)
+
+#include "pfctl.h"
+#include "pfctl_parser.h"
+
+#ifdef PFDEBUG
+#include <sys/stdarg.h>
+#define DPFPRINTF(format, x...) fprintf(stderr, format , ##x)
+#else
+#define DPFPRINTF(format, x...) ((void)0)
+#endif /* PFDEBUG */
+
+struct pfctl_anchor_global pf_anchors;
+extern struct pfctl_anchor pf_main_anchor;
+extern struct pfctl_eth_anchor pf_eth_main_anchor;
+#undef V_pf_anchors
+#define V_pf_anchors pf_anchors
+#undef pf_main_ruleset
+#define pf_main_ruleset pf_main_anchor.ruleset
+
+static __inline int pf_anchor_compare(struct pfctl_anchor *,
+ struct pfctl_anchor *);
+static struct pfctl_anchor *pf_find_anchor(const char *);
+
+RB_GENERATE(pfctl_anchor_global, pfctl_anchor, entry_global,
+ pf_anchor_compare);
+RB_GENERATE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare);
+
+static __inline int
+pf_anchor_compare(struct pfctl_anchor *a, struct pfctl_anchor *b)
+{
+ int c = strcmp(a->path, b->path);
+
+ return (c ? (c < 0 ? -1 : 1) : 0);
+}
+
+int
+pf_get_ruleset_number(u_int8_t action)
+{
+ switch (action) {
+ case PF_SCRUB:
+ case PF_NOSCRUB:
+ return (PF_RULESET_SCRUB);
+ break;
+ case PF_PASS:
+ case PF_DROP:
+ case PF_MATCH:
+ return (PF_RULESET_FILTER);
+ break;
+ case PF_NAT:
+ case PF_NONAT:
+ return (PF_RULESET_NAT);
+ break;
+ case PF_BINAT:
+ case PF_NOBINAT:
+ return (PF_RULESET_BINAT);
+ break;
+ case PF_RDR:
+ case PF_NORDR:
+ return (PF_RULESET_RDR);
+ break;
+ default:
+ return (PF_RULESET_MAX);
+ break;
+ }
+}
+
+void
+pf_init_ruleset(struct pfctl_ruleset *ruleset)
+{
+ int i;
+
+ memset(ruleset, 0, sizeof(struct pfctl_ruleset));
+ for (i = 0; i < PF_RULESET_MAX; i++) {
+ TAILQ_INIT(&ruleset->rules[i].queues[0]);
+ TAILQ_INIT(&ruleset->rules[i].queues[1]);
+ ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0];
+ ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1];
+ }
+}
+
+static struct pfctl_anchor *
+pf_find_anchor(const char *path)
+{
+ struct pfctl_anchor *key, *found;
+
+ key = (struct pfctl_anchor *)rs_malloc(sizeof(*key));
+ if (key == NULL)
+ return (NULL);
+ strlcpy(key->path, path, sizeof(key->path));
+ found = RB_FIND(pfctl_anchor_global, &V_pf_anchors, key);
+ rs_free(key);
+ return (found);
+}
+
+struct pfctl_ruleset *
+pf_find_ruleset(const char *path)
+{
+ struct pfctl_anchor *anchor;
+
+ while (*path == '/')
+ path++;
+ if (!*path)
+ return (&pf_main_ruleset);
+ anchor = pf_find_anchor(path);
+ if (anchor == NULL)
+ return (NULL);
+ else
+ return (&anchor->ruleset);
+}
+
+struct pfctl_ruleset *
+pf_find_or_create_ruleset(const char *path)
+{
+ char *p, *q, *r;
+ struct pfctl_ruleset *ruleset;
+ struct pfctl_anchor *anchor = NULL, *dup, *parent = NULL;
+
+ if (path[0] == 0)
+ return (&pf_main_ruleset);
+ while (*path == '/')
+ path++;
+ ruleset = pf_find_ruleset(path);
+ if (ruleset != NULL)
+ return (ruleset);
+ p = (char *)rs_malloc(MAXPATHLEN);
+ if (p == NULL)
+ return (NULL);
+ strlcpy(p, path, MAXPATHLEN);
+ while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
+ *q = 0;
+ if ((ruleset = pf_find_ruleset(p)) != NULL) {
+ parent = ruleset->anchor;
+ break;
+ }
+ }
+ if (q == NULL)
+ q = p;
+ else
+ q++;
+ strlcpy(p, path, MAXPATHLEN);
+ if (!*q) {
+ rs_free(p);
+ return (NULL);
+ }
+ while ((r = strchr(q, '/')) != NULL || *q) {
+ if (r != NULL)
+ *r = 0;
+ if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
+ (parent != NULL && strlen(parent->path) >=
+ MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
+ rs_free(p);
+ return (NULL);
+ }
+ anchor = (struct pfctl_anchor *)rs_malloc(sizeof(*anchor));
+ if (anchor == NULL) {
+ rs_free(p);
+ return (NULL);
+ }
+ RB_INIT(&anchor->children);
+ strlcpy(anchor->name, q, sizeof(anchor->name));
+ if (parent != NULL) {
+ strlcpy(anchor->path, parent->path,
+ sizeof(anchor->path));
+ strlcat(anchor->path, "/", sizeof(anchor->path));
+ }
+ strlcat(anchor->path, anchor->name, sizeof(anchor->path));
+ if ((dup = RB_INSERT(pfctl_anchor_global, &V_pf_anchors, anchor)) !=
+ NULL) {
+ printf("pf_find_or_create_ruleset: RB_INSERT1 "
+ "'%s' '%s' collides with '%s' '%s'\n",
+ anchor->path, anchor->name, dup->path, dup->name);
+ rs_free(anchor);
+ rs_free(p);
+ return (NULL);
+ }
+ if (parent != NULL) {
+ anchor->parent = parent;
+ if ((dup = RB_INSERT(pfctl_anchor_node, &parent->children,
+ anchor)) != NULL) {
+ printf("pf_find_or_create_ruleset: "
+ "RB_INSERT2 '%s' '%s' collides with "
+ "'%s' '%s'\n", anchor->path, anchor->name,
+ dup->path, dup->name);
+ RB_REMOVE(pfctl_anchor_global, &V_pf_anchors,
+ anchor);
+ rs_free(anchor);
+ rs_free(p);
+ return (NULL);
+ }
+ }
+ pf_init_ruleset(&anchor->ruleset);
+ anchor->ruleset.anchor = anchor;
+ parent = anchor;
+ if (r != NULL)
+ q = r + 1;
+ else
+ *q = 0;
+ }
+ rs_free(p);
+ return (&anchor->ruleset);
+}
+
+void
+pf_remove_if_empty_ruleset(struct pfctl_ruleset *ruleset)
+{
+ struct pfctl_anchor *parent;
+ int i;
+
+ while (ruleset != NULL) {
+ if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL ||
+ !RB_EMPTY(&ruleset->anchor->children) ||
+ ruleset->anchor->refcnt > 0 || ruleset->tables > 0 ||
+ ruleset->topen)
+ return;
+ for (i = 0; i < PF_RULESET_MAX; ++i)
+ if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) ||
+ !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) ||
+ ruleset->rules[i].inactive.open)
+ return;
+ RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, ruleset->anchor);
+ if ((parent = ruleset->anchor->parent) != NULL)
+ RB_REMOVE(pfctl_anchor_node, &parent->children,
+ ruleset->anchor);
+ rs_free(ruleset->anchor);
+ if (parent == NULL)
+ return;
+ ruleset = &parent->ruleset;
+ }
+}
+
+void
+pf_remove_if_empty_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
+{
+ struct pfctl_eth_anchor *parent;
+
+ return;
+ while (ruleset != NULL) {
+ if (ruleset == &pf_eth_main_anchor.ruleset ||
+ ruleset->anchor == NULL || ruleset->anchor->refcnt > 0)
+ return;
+ if (!TAILQ_EMPTY(&ruleset->rules))
+ return;
+ rs_free(ruleset->anchor);
+ if (parent == NULL)
+ return;
+ ruleset = &parent->ruleset;
+ }
+}
+
+void
+pf_init_eth_ruleset(struct pfctl_eth_ruleset *ruleset)
+{
+
+ memset(ruleset, 0, sizeof(*ruleset));
+ TAILQ_INIT(&ruleset->rules);
+}
+
+
+static struct pfctl_eth_anchor*
+_pf_find_eth_anchor(struct pfctl_eth_anchor *anchor, const char *path)
+{
+ struct pfctl_eth_rule *r;
+ struct pfctl_eth_anchor *a;
+
+ if (strcmp(path, anchor->path) == 0)
+ return (anchor);
+
+ TAILQ_FOREACH(r, &anchor->ruleset.rules, entries) {
+ if (! r->anchor)
+ continue;
+
+ /* Step into anchor */
+ a = _pf_find_eth_anchor(r->anchor, path);
+ if (a)
+ return (a);
+ }
+
+ return (NULL);
+}
+
+static struct pfctl_eth_anchor*
+pf_find_eth_anchor(const char *path)
+{
+ return (_pf_find_eth_anchor(&pf_eth_main_anchor, path));
+}
+
+static struct pfctl_eth_ruleset*
+pf_find_eth_ruleset(const char *path)
+{
+ struct pfctl_eth_anchor *anchor;
+
+ while (*path == '/')
+ path++;
+ if (!*path)
+ return (&pf_eth_main_anchor.ruleset);
+ anchor = pf_find_eth_anchor(path);
+ if (anchor == NULL)
+ return (NULL);
+ else
+ return (&anchor->ruleset);
+}
+
+struct pfctl_eth_ruleset *
+pf_find_or_create_eth_ruleset(const char *path)
+{
+ char *p, *q, *r;
+ struct pfctl_eth_ruleset *ruleset;
+ struct pfctl_eth_anchor *anchor = NULL, *parent = NULL;
+
+ if (path[0] == 0)
+ return (&pf_eth_main_anchor.ruleset);
+ while (*path == '/')
+ path++;
+ ruleset = pf_find_eth_ruleset(path);
+ if (ruleset != NULL)
+ return (ruleset);
+ p = (char *)rs_malloc(MAXPATHLEN);
+ if (p == NULL)
+ return (NULL);
+ strlcpy(p, path, MAXPATHLEN);
+ while (parent == NULL && (q = strrchr(p, '/')) != NULL) {
+ *q = 0;
+ if ((ruleset = pf_find_eth_ruleset(p)) != NULL) {
+ parent = ruleset->anchor;
+ break;
+ }
+ }
+ if (q == NULL)
+ q = p;
+ else
+ q++;
+ strlcpy(p, path, MAXPATHLEN);
+ if (!*q) {
+ rs_free(p);
+ return (NULL);
+ }
+ while ((r = strchr(q, '/')) != NULL || *q) {
+ if (r != NULL)
+ *r = 0;
+ if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE ||
+ (parent != NULL && strlen(parent->path) >=
+ MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) {
+ rs_free(p);
+ return (NULL);
+ }
+ anchor = (struct pfctl_eth_anchor *)rs_malloc(sizeof(*anchor));
+ if (anchor == NULL) {
+ rs_free(p);
+ return (NULL);
+ }
+ strlcpy(anchor->name, q, sizeof(anchor->name));
+ if (parent != NULL) {
+ strlcpy(anchor->path, parent->path,
+ sizeof(anchor->path));
+ strlcat(anchor->path, "/", sizeof(anchor->path));
+ }
+ strlcat(anchor->path, anchor->name, sizeof(anchor->path));
+ if (parent != NULL)
+ anchor->parent = parent;
+ pf_init_eth_ruleset(&anchor->ruleset);
+ anchor->ruleset.anchor = anchor;
+ parent = anchor;
+ if (r != NULL)
+ q = r + 1;
+ else
+ *q = 0;
+ }
+ rs_free(p);
+ return (&anchor->ruleset);
+}
+
+int
+pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s,
+ const char *name)
+{
+ char *p, *path;
+ struct pfctl_ruleset *ruleset;
+
+ r->anchor = NULL;
+ r->anchor_relative = 0;
+ r->anchor_wildcard = 0;
+ if (!name[0])
+ return (0);
+ path = (char *)rs_malloc(MAXPATHLEN);
+ if (path == NULL)
+ return (1);
+ if (name[0] == '/')
+ strlcpy(path, name + 1, MAXPATHLEN);
+ else {
+ /* relative path */
+ r->anchor_relative = 1;
+ if (s->anchor == NULL || !s->anchor->path[0])
+ path[0] = 0;
+ else
+ strlcpy(path, s->anchor->path, MAXPATHLEN);
+ while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
+ if (!path[0]) {
+ printf("pfctl_anchor_setup: .. beyond root\n");
+ rs_free(path);
+ return (1);
+ }
+ if ((p = strrchr(path, '/')) != NULL)
+ *p = 0;
+ else
+ path[0] = 0;
+ r->anchor_relative++;
+ name += 3;
+ }
+ if (path[0])
+ strlcat(path, "/", MAXPATHLEN);
+ strlcat(path, name, MAXPATHLEN);
+ }
+ if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
+ r->anchor_wildcard = 1;
+ *p = 0;
+ }
+ ruleset = pf_find_or_create_ruleset(path);
+ rs_free(path);
+ if (ruleset == NULL || ruleset->anchor == NULL) {
+ printf("pfctl_anchor_setup: ruleset\n");
+ return (1);
+ }
+ r->anchor = ruleset->anchor;
+ r->anchor->refcnt++;
+ return (0);
+}
+
+int
+pfctl_eth_anchor_setup(struct pfctl *pf, struct pfctl_eth_rule *r,
+ const struct pfctl_eth_ruleset *s, const char *name)
+{
+ char *p, *path;
+ struct pfctl_eth_ruleset *ruleset;
+
+ r->anchor = NULL;
+ if (!name[0])
+ return (0);
+ path = (char *)rs_malloc(MAXPATHLEN);
+ if (path == NULL)
+ return (1);
+ if (name[0] == '/')
+ strlcpy(path, name + 1, MAXPATHLEN);
+ else {
+ /* relative path */
+ if (s->anchor == NULL || !s->anchor->path[0])
+ path[0] = 0;
+ else
+ strlcpy(path, s->anchor->path, MAXPATHLEN);
+ while (name[0] == '.' && name[1] == '.' && name[2] == '/') {
+ if (!path[0]) {
+ printf("%s: .. beyond root\n", __func__);
+ rs_free(path);
+ return (1);
+ }
+ if ((p = strrchr(path, '/')) != NULL)
+ *p = 0;
+ else
+ path[0] = 0;
+ name += 3;
+ }
+ if (path[0])
+ strlcat(path, "/", MAXPATHLEN);
+ strlcat(path, name, MAXPATHLEN);
+ }
+ if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) {
+ *p = 0;
+ }
+ ruleset = pf_find_or_create_eth_ruleset(path);
+ rs_free(path);
+ if (ruleset == NULL || ruleset->anchor == NULL) {
+ printf("%s: ruleset\n", __func__);
+ return (1);
+ }
+ r->anchor = ruleset->anchor;
+ r->anchor->refcnt++;
+ return (0);
+}
diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8
new file mode 100644
index 000000000000..58de54cdf923
--- /dev/null
+++ b/sbin/pfctl/pfctl.8
@@ -0,0 +1,830 @@
+.\" $OpenBSD: pfctl.8,v 1.138 2008/06/10 20:55:02 mcbride Exp $
+.\"
+.\" Copyright (c) 2001 Kjell Wooding. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. The name of the author may not be used to endorse or promote products
+.\" derived from this software without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 28, 2025
+.Dt PFCTL 8
+.Os
+.Sh NAME
+.Nm pfctl
+.Nd control the packet filter (PF) device
+.Sh SYNOPSIS
+.Nm pfctl
+.Bk -words
+.Op Fl AdeghMmNnOPqRrvz
+.Op Fl a Ar anchor
+.Oo Fl D Ar macro Ns =
+.Ar value Oc
+.Op Fl F Ar modifier
+.Op Fl f Ar file
+.Op Fl i Ar interface
+.Op Fl K Ar host | network
+.Xo
+.Oo Fl k
+.Ar host | network | label | id | gateway | nat
+.Oc Xc
+.Op Fl o Ar level
+.Op Fl p Ar device
+.Op Fl s Ar modifier
+.Xo
+.Oo Fl t Ar table
+.Fl T Ar command
+.Op Ar address ...
+.Oc Xc
+.Op Fl x Ar level
+.Ek
+.Sh DESCRIPTION
+The
+.Nm
+utility communicates with the packet filter device using the
+ioctl interface described in
+.Xr pf 4 .
+It allows ruleset and parameter configuration and retrieval of status
+information from the packet filter.
+.Pp
+Packet filtering restricts the types of packets that pass through
+network interfaces entering or leaving the host based on filter
+rules as described in
+.Xr pf.conf 5 .
+The packet filter can also replace addresses and ports of packets.
+Replacing source addresses and ports of outgoing packets is called
+NAT (Network Address Translation) and is used to connect an internal
+network (usually reserved address space) to an external one (the
+Internet) by making all connections to external hosts appear to
+come from the gateway.
+Replacing destination addresses and ports of incoming packets
+is used to redirect connections to different hosts and/or ports.
+A combination of both translations, bidirectional NAT, is also
+supported.
+Translation rules are described in
+.Xr pf.conf 5 .
+.Pp
+When the variable
+.Va pf_enable
+is set to
+.Dv YES
+in
+.Xr rc.conf 5 ,
+the rule file specified with the variable
+.Va pf_rules
+is loaded automatically by the
+.Xr rc 8
+scripts and the packet filter is enabled.
+.Pp
+The packet filter does not itself forward packets between interfaces.
+Forwarding can be enabled by setting the
+.Xr sysctl 8
+variables
+.Em net.inet.ip.forwarding
+and/or
+.Em net.inet6.ip6.forwarding
+to 1.
+Set them permanently in
+.Xr sysctl.conf 5 .
+.Pp
+At least one option must be specified.
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl A
+Load only the queue rules present in the rule file.
+Other rules and options are ignored.
+.It Fl a Ar anchor
+Apply flags
+.Fl f ,
+.Fl F ,
+.Fl s ,
+.Fl T ,
+and
+.Fl z
+only to the rules in the specified
+.Ar anchor .
+In addition to the main ruleset,
+.Nm
+can load and manipulate additional rulesets by name,
+called anchors.
+The main ruleset is the default anchor.
+.Pp
+Anchors are referenced by name and may be nested,
+with the various components of the anchor path separated by
+.Sq /
+characters, similar to how file system hierarchies are laid out.
+The last component of the anchor path is where ruleset operations are
+performed.
+.Pp
+Evaluation of
+.Ar anchor
+rules from the main ruleset is described in
+.Xr pf.conf 5 .
+.Pp
+For example, the following will show all filter rules (see the
+.Fl s
+flag below) inside the anchor
+.Dq authpf/smith(1234) ,
+which would have been created for user
+.Dq smith
+by
+.Xr authpf 8 ,
+PID 1234:
+.Bd -literal -offset indent
+# pfctl -a "authpf/smith(1234)" -s rules
+.Ed
+.Pp
+Private tables can also be put inside anchors, either by having table
+statements in the
+.Xr pf.conf 5
+file that is loaded in the anchor, or by using regular table commands, as in:
+.Bd -literal -offset indent
+# pfctl -a foo/bar -t mytable -T add 1.2.3.4 5.6.7.8
+.Ed
+.Pp
+When a rule referring to a table is loaded in an anchor, the rule will use the
+private table if one is defined, and then fall back to the table defined in the
+main ruleset, if there is one.
+This is similar to C rules for variable scope.
+It is possible to create distinct tables with the same name in the global
+ruleset and in an anchor, but this is often bad design and a warning will be
+issued in that case.
+.Pp
+By default, recursive inline printing of anchors applies only to unnamed
+anchors specified inline in the ruleset.
+If the anchor name is terminated with a
+.Sq *
+character, the
+.Fl s
+flag will recursively print all anchors in a brace delimited block.
+For example the following will print the
+.Dq authpf
+ruleset recursively:
+.Bd -literal -offset indent
+# pfctl -a 'authpf/*' -sr
+.Ed
+.Pp
+To print the main ruleset recursively, specify only
+.Sq *
+as the anchor name:
+.Bd -literal -offset indent
+# pfctl -a '*' -sr
+.Ed
+.Pp
+To flush all rulesets and tables recursively, specify only
+.Sq *
+as the anchor name:
+.Bd -literal -offset indent
+# pfctl -a '*' -Fa
+.Ed
+.It Fl D Ar macro Ns = Ns Ar value
+Define
+.Ar macro
+to be set to
+.Ar value
+on the command line.
+Overrides the definition of
+.Ar macro
+in the ruleset.
+.It Fl d
+Disable the packet filter.
+.It Fl e
+Enable the packet filter.
+.It Fl F Ar modifier
+Flush the filter parameters specified by
+.Ar modifier
+(may be abbreviated):
+.Pp
+.Bl -tag -width xxxxxxxxx -compact
+.It Cm nat
+Flush the NAT rules.
+.It Cm queue
+Flush the queue rules.
+.It Cm ethernet
+Flush the Ethernet filter rules.
+.It Cm rules
+Flush the filter rules.
+.It Cm states
+Flush the state table (NAT and filter).
+.It Cm Sources
+Flush the source tracking table.
+.It Cm info
+Flush the filter information (statistics that are not bound to rules).
+.It Cm Tables
+Flush the tables.
+.It Cm osfp
+Flush the passive operating system fingerprints.
+.It Cm Reset
+Reset limits, timeouts and other options back to default settings.
+See the OPTIONS section in
+.Xr pf.conf 5
+for details.
+.It Cm all
+Flush all of the above.
+.El
+.Pp
+If
+.Fl a
+is specified as well and
+.Ar anchor
+is terminated with a
+.Sq *
+character,
+.Cm rules ,
+.Cm Tables
+and
+.Cm all
+flush the given anchor recursively.
+.It Fl f Ar file
+Load the rules contained in
+.Ar file .
+This
+.Ar file
+may contain macros, tables, options, and normalization, queueing,
+translation, and filtering rules.
+With the exception of macros and tables, the statements must appear in that
+order.
+.It Fl g
+Include output helpful for debugging.
+.It Fl h
+Help.
+.It Fl i Ar interface
+Restrict the operation to the given
+.Ar interface .
+.It Fl K Ar host | network
+Kill all of the source tracking entries originating from the specified
+.Ar host
+or
+.Ar network .
+A second
+.Fl K Ar host
+or
+.Fl K Ar network
+option may be specified, which will kill all the source tracking
+entries from the first host/network to the second.
+.It Xo
+.Fl k
+.Ar host | network | label | id | key | gateway | nat
+.Xc
+Kill all of the state entries matching the specified
+.Ar host ,
+.Ar network ,
+.Ar label ,
+.Ar id ,
+.Ar key ,
+.Ar gateway,
+or
+.Ar nat.
+.Pp
+For example, to kill all of the state entries originating from
+.Dq host :
+.Pp
+.Dl # pfctl -k host
+.Pp
+A second
+.Fl k Ar host
+or
+.Fl k Ar network
+option may be specified, which will kill all the state entries
+from the first host/network to the second.
+To kill all of the state entries from
+.Dq host1
+to
+.Dq host2 :
+.Pp
+.Dl # pfctl -k host1 -k host2
+.Pp
+To kill all states originating from 192.168.1.0/24 to 172.16.0.0/16:
+.Pp
+.Dl # pfctl -k 192.168.1.0/24 -k 172.16.0.0/16
+.Pp
+A network prefix length of 0 can be used as a wildcard.
+To kill all states with the target
+.Dq host2 :
+.Pp
+.Dl # pfctl -k 0.0.0.0/0 -k host2
+.Pp
+It is also possible to kill states by rule label, state key or state ID.
+In this mode the first
+.Fl k
+argument is used to specify the type
+of the second argument.
+The following command would kill all states that have been created
+from rules carrying the label
+.Dq foobar :
+.Pp
+.Dl # pfctl -k label -k foobar
+.Pp
+To kill one specific state by its key
+(protocol, host1, port1, direction, host2 and port2 in the same format
+of pfctl -s state),
+use the
+.Ar key
+modifier and as a second argument the state key.
+To kill a state whose protocol is TCP and originating from
+10.0.0.101:32123 to 10.0.0.1:80 use:
+.Pp
+.Dl # pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'
+.Pp
+To kill one specific state by its unique state ID
+(as shown by pfctl -s state -vv),
+use the
+.Ar id
+modifier and as a second argument the state ID and optional creator ID.
+To kill a state with ID 4823e84500000003 use:
+.Pp
+.Dl # pfctl -k id -k 4823e84500000003
+.Pp
+To kill a state with ID 4823e84500000018 created from a backup
+firewall with hostid 00000002 use:
+.Pp
+.Dl # pfctl -k id -k 4823e84500000018/2
+.Pp
+It is also possible to kill states created from a rule with the route-to/reply-to
+parameter set to route the connection through a particular gateway.
+Note that rules routing via the default routing table (not via a route-to
+rule) will have their rt_addr set as 0.0.0.0 or ::.
+To kill all states using a gateway of 192.168.0.1 use:
+.Pp
+.Dl # pfctl -k gateway -k 192.168.0.1
+.Pp
+A network prefix length can also be specified.
+To kill all states using a gateway in 192.168.0.0/24:
+.Pp
+.Dl # pfctl -k gateway -k 192.168.0.0/24
+.Pp
+States can also be killed based on their pre-NAT address:
+.Pp
+.Dl # pfctl -k nat -k 192.168.0.1
+.Pp
+.It Fl M
+Kill matching states in the opposite direction (on other interfaces) when
+killing states.
+This applies to states killed using the -k option and also will apply to the
+flush command when flushing states.
+This is useful when an interface is specified when flushing states.
+Example:
+.Pp
+.Dl # pfctl -M -i interface -Fs
+.Pp
+.It Fl m
+Merge in explicitly given options without resetting those
+which are omitted.
+Allows single options to be modified without disturbing the others:
+.Bd -literal -offset indent
+# echo "set loginterface fxp0" | pfctl -mf -
+.Ed
+.It Fl N
+Load only the NAT rules present in the rule file.
+Other rules and options are ignored.
+.It Fl n
+Do not actually load rules, just parse them.
+.It Fl O
+Load only the options present in the rule file.
+Other rules and options are ignored.
+.It Fl o Ar level
+Control the ruleset optimizer, overriding any rule file settings.
+.Pp
+.Bl -tag -width xxxxxxxxx -compact
+.It Cm none
+Disable the ruleset optimizer.
+.It Cm basic
+Enable basic ruleset optimizations.
+This is the default behaviour.
+.It Cm profile
+Enable basic ruleset optimizations with profiling.
+.El
+.Pp
+For further information on the ruleset optimizer, see
+.Xr pf.conf 5 .
+.It Fl P
+Do not perform service name lookup for port specific rules,
+instead display the ports numerically.
+.It Fl p Ar device
+Use the device file
+.Ar device
+instead of the default
+.Pa /dev/pf .
+.It Fl q
+Only print errors and warnings.
+.It Fl R
+Load only the filter rules present in the rule file.
+Other rules and options are ignored.
+.It Fl r
+Perform reverse DNS lookups on states and tables when displaying them.
+.Fl N
+and
+.Fl r
+are mutually exclusive.
+.It Fl s Ar modifier Op Fl R Ar id
+Show the filter parameters specified by
+.Ar modifier
+(may be abbreviated):
+.Pp
+.Bl -tag -width xxxxxxxxxxx -compact
+.It Cm nat
+Show the currently loaded NAT rules.
+.It Cm queue
+Show the currently loaded queue rules.
+When used together with
+.Fl v ,
+per-queue statistics are also shown.
+When used together with
+.Fl v v ,
+.Nm
+will loop and show updated queue statistics every five seconds, including
+measured bandwidth and packets per second.
+.It Cm ether
+Show the currently loaded Ethernet rules.
+When used together with
+.Fl v ,
+the per-rule statistics (number of evaluations,
+packets, and bytes) are also shown.
+.It Cm rules
+Show the currently loaded filter rules.
+When used together with
+.Fl v ,
+the per-rule statistics (number of evaluations,
+packets, and bytes) are also shown.
+When used together with
+.Fl g
+or
+.Fl vv ,
+expired rules
+.Pq marked as Dq # expired
+are also shown.
+Note that the
+.Dq skip step
+optimization done automatically by the kernel
+will skip evaluation of rules where possible.
+Packets passed statefully are counted in the rule that created the state
+(even though the rule is not evaluated more than once for the entire
+connection).
+.It Cm Anchors
+Show the currently loaded anchors directly attached to the main ruleset.
+If
+.Fl a Ar anchor
+is specified as well, the anchors loaded directly below the given
+.Ar anchor
+are shown instead.
+If
+.Fl v
+is specified, all anchors attached under the target anchor will be
+displayed recursively.
+.It Cm states
+Show the contents of the state table.
+.It Cm Sources
+Show the contents of the source tracking table.
+.It Cm info
+Show filter information (statistics and counters).
+When used together with
+.Fl v ,
+source tracking statistics, the firewall's 32-bit hostid number and the
+main ruleset's MD5 checksum for use with
+.Xr pfsync 4
+are also shown.
+.It Cm Running
+Show the running status and provide a non-zero exit status when disabled.
+.It Cm labels
+Show per-rule statistics (label, evaluations, packets total, bytes total,
+packets in, bytes in, packets out, bytes out, state creations) of
+filter rules with labels, useful for accounting.
+.It Cm timeouts
+Show the current global timeouts.
+.It Cm memory
+Show the current pool memory hard limits.
+.It Cm Tables
+Show the list of tables.
+.It Cm osfp
+Show the list of operating system fingerprints.
+.It Cm Interfaces
+Show the list of interfaces and interface groups available to PF.
+When used together with
+.Fl v ,
+it additionally lists which interfaces have skip rules activated.
+When used together with
+.Fl vv ,
+interface statistics are also shown.
+.Fl i
+can be used to select an interface or a group of interfaces.
+.It Cm all
+Show all of the above, except for the lists of interfaces and operating
+system fingerprints.
+.El
+.Pp
+Counters shown with
+.Fl s Cm info
+are:
+.Pp
+.Bl -tag -width xxxxxxxxxxxxxx -compact
+.It match
+explicit rule match
+.It bad-offset
+currently unused
+.It fragment
+invalid fragments dropped
+.It short
+short packets dropped
+.It normalize
+dropped by normalizer: illegal packets
+.It memory
+memory could not be allocated
+.It bad-timestamp
+bad TCP timestamp; RFC 1323
+.It congestion
+network interface queue congested
+.It ip-option
+bad IP/IPv6 options
+.It proto-cksum
+invalid protocol checksum
+.It state-mismatch
+packet was associated with a state entry, but sequence numbers did not match
+.It state-insert
+state insertion failure
+.It state-limit
+configured state limit was reached
+.It src-limit
+source node/connection limit
+.It synproxy
+dropped by synproxy
+.It map-failed
+address mapping failed
+.It translate
+no free ports in translation port range
+.El
+.It Fl S
+Do not perform domain name resolution.
+If a name cannot be resolved without DNS, an error will be reported.
+.It Fl t Ar table Fl T Ar command Op Ar address ...
+Specify the
+.Ar command
+(may be abbreviated) to apply to
+.Ar table .
+Commands include:
+.Pp
+.Bl -tag -width "expire number" -compact
+.It Cm add
+Add one or more addresses to a table.
+Automatically create a persistent table if it does not exist.
+.It Cm delete
+Delete one or more addresses from a table.
+.It Cm expire Ar number
+Delete addresses which had their statistics cleared more than
+.Ar number
+seconds ago.
+For entries which have never had their statistics cleared,
+.Ar number
+refers to the time they were added to the table.
+.It Cm flush
+Flush all addresses in a table.
+.It Cm kill
+Kill a table.
+.It Cm replace
+Replace the addresses of the table.
+Automatically create a persistent table if it does not exist.
+.It Cm show
+Show the content (addresses) of a table.
+.It Cm test
+Test if the given addresses match a table.
+.It Cm zero Op Ar address ...
+Clear all the statistics of a table, or only for specified addresses.
+.It Cm reset
+Clear statistics only for addresses with non-zero statistics. Addresses
+with counter values at zero and their
+.Dq Cleared
+timestamp are left untouched.
+.It Cm load
+Load only the table definitions from
+.Xr pf.conf 5 .
+This is used in conjunction with the
+.Fl f
+flag, as in:
+.Bd -literal -offset indent
+# pfctl -Tl -f pf.conf
+.Ed
+.El
+.Pp
+For the
+.Cm add ,
+.Cm delete ,
+.Cm replace ,
+and
+.Cm test
+commands, the list of addresses can be specified either directly on the command
+line and/or in an unformatted text file, using the
+.Fl f
+flag.
+Comments starting with a
+.Sq #
+or
+.Sq \;
+are allowed in the text file.
+With these commands, the
+.Fl v
+flag can also be used once or twice, in which case
+.Nm
+will print the
+detailed result of the operation for each individual address, prefixed by
+one of the following letters:
+.Pp
+.Bl -tag -width XXX -compact
+.It A
+The address/network has been added.
+.It C
+The address/network has been changed (negated).
+.It D
+The address/network has been deleted.
+.It M
+The address matches
+.Po
+.Cm test
+operation only
+.Pc .
+.It X
+The address/network is duplicated and therefore ignored.
+.It Y
+The address/network cannot be added/deleted due to conflicting
+.Sq \&!
+attributes.
+.It Z
+The address/network has been cleared (statistics).
+.El
+.Pp
+Each table can maintain a set of counters that can be retrieved using the
+.Fl v
+flag of
+.Nm .
+For example, the following commands define a wide open firewall which will keep
+track of packets going to or coming from the
+.Ox
+FTP server.
+The following commands configure the firewall and send 10 pings to the FTP
+server:
+.Bd -literal -offset indent
+# printf "table <test> counters { ftp.openbsd.org }\en \e
+ pass out to <test>\en" | pfctl -f-
+# ping -qc10 ftp.openbsd.org
+.Ed
+.Pp
+We can now use the table
+.Cm show
+command to output, for each address and packet direction, the number of packets
+and bytes that are being passed or blocked by rules referencing the table.
+The time at which the current accounting started is also shown with the
+.Dq Cleared
+line.
+.Bd -literal -offset indent
+# pfctl -t test -vTshow
+ 129.128.5.191
+ Cleared: Thu Feb 13 18:55:18 2003
+ In/Block: [ Packets: 0 Bytes: 0 ]
+ In/Pass: [ Packets: 10 Bytes: 840 ]
+ Out/Block: [ Packets: 0 Bytes: 0 ]
+ Out/Pass: [ Packets: 10 Bytes: 840 ]
+.Ed
+.Pp
+Similarly, it is possible to view global information about the tables
+by using the
+.Fl v
+modifier twice and the
+.Fl s
+.Cm Tables
+command.
+This will display the number of addresses on each table,
+the number of rules which reference the table, and the global
+packet statistics for the whole table:
+.Bd -literal -offset indent
+# pfctl -vvsTables
+--a-r-C test
+ Addresses: 1
+ Cleared: Thu Feb 13 18:55:18 2003
+ References: [ Anchors: 0 Rules: 1 ]
+ Evaluations: [ NoMatch: 3496 Match: 1 ]
+ In/Block: [ Packets: 0 Bytes: 0 ]
+ In/Pass: [ Packets: 10 Bytes: 840 ]
+ In/XPass: [ Packets: 0 Bytes: 0 ]
+ Out/Block: [ Packets: 0 Bytes: 0 ]
+ Out/Pass: [ Packets: 10 Bytes: 840 ]
+ Out/XPass: [ Packets: 0 Bytes: 0 ]
+.Ed
+.Pp
+As we can see here, only one packet \- the initial ping request \- matched the
+table, but all packets passing as the result of the state are correctly
+accounted for.
+Reloading the table(s) or ruleset will not affect packet accounting in any way.
+The two
+.Dq XPass
+counters are incremented instead of the
+.Dq Pass
+counters when a
+.Dq stateful
+packet is passed but does not match the table anymore.
+This will happen in our example if someone flushes the table while the
+.Xr ping 8
+command is running.
+.Pp
+When used with a single
+.Fl v ,
+.Nm
+will only display the first line containing the table flags and name.
+The flags are defined as follows:
+.Pp
+.Bl -tag -width XXX -compact
+.It c
+For constant tables, which cannot be altered outside
+.Xr pf.conf 5 .
+.It p
+For persistent tables, which do not get automatically killed when no rules
+refer to them.
+.It a
+For tables which are part of the
+.Em active
+tableset.
+Tables without this flag do not really exist, cannot contain addresses, and are
+only listed if the
+.Fl g
+flag is given.
+.It i
+For tables which are part of the
+.Em inactive
+tableset.
+This flag can only be witnessed briefly during the loading of
+.Xr pf.conf 5 .
+.It r
+For tables which are referenced (used) by rules.
+.It h
+This flag is set when a table in the main ruleset is hidden by one or more
+tables of the same name from anchors attached below it.
+.It C
+This flag is set when per-address counters are enabled on the table.
+.El
+.It Fl v
+Produce more verbose output.
+A second use of
+.Fl v
+will produce even more verbose output including ruleset warnings.
+See the previous section for its effect on table commands.
+.It Fl x Ar level
+Set the debug
+.Ar level
+(may be abbreviated) to one of the following:
+.Pp
+.Bl -tag -width xxxxxxxxxxxx -compact
+.It Fl x Cm none
+Do not generate debug messages.
+.It Fl x Cm urgent
+Generate debug messages only for serious errors.
+.It Fl x Cm misc
+Generate debug messages for various errors.
+.It Fl x Cm loud
+Generate debug messages for common conditions.
+.El
+.It Fl z
+Clear per-rule statistics.
+.El
+.Sh FILES
+.Bl -tag -width "/etc/pf.conf" -compact
+.It Pa /etc/pf.conf
+Packet filter rules file.
+.It Pa /etc/pf.os
+Passive operating system fingerprint database.
+.El
+.Sh SEE ALSO
+.Xr pf 4 ,
+.Xr pf.conf 5 ,
+.Xr pf.os 5 ,
+.Xr rc.conf 5 ,
+.Xr services 5 ,
+.Xr sysctl.conf 5 ,
+.Xr authpf 8 ,
+.Xr ftp-proxy 8 ,
+.Xr rc 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+program and the
+.Xr pf 4
+filter mechanism appeared in
+.Ox 3.0 .
+They first appeared in
+.Fx 5.3
+ported from the version in
+.Ox 3.5
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
new file mode 100644
index 000000000000..02d6c9c84a32
--- /dev/null
+++ b/sbin/pfctl/pfctl.c
@@ -0,0 +1,3759 @@
+/* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * Copyright (c) 2002,2003 Henning Brauer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/cdefs.h>
+#define PFIOC_USE_LATEST
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/endian.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+#include <net/altq/altq.h>
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libpfctl.h>
+#include <limits.h>
+#include <netdb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <libgen.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void usage(void);
+int pfctl_enable(int, int);
+int pfctl_disable(int, int);
+void pfctl_clear_stats(struct pfctl_handle *, int);
+void pfctl_get_skip_ifaces(void);
+void pfctl_check_skip_ifaces(char *);
+void pfctl_adjust_skip_ifaces(struct pfctl *);
+void pfctl_clear_interface_flags(int, int);
+void pfctl_flush_eth_rules(int, int, char *);
+int pfctl_flush_rules(int, int, char *);
+void pfctl_flush_nat(int, int, char *);
+int pfctl_clear_altq(int, int);
+void pfctl_clear_src_nodes(int, int);
+void pfctl_clear_iface_states(int, const char *, int);
+struct addrinfo *
+ pfctl_addrprefix(char *, struct pf_addr *, int);
+void pfctl_kill_src_nodes(int, int);
+void pfctl_net_kill_states(int, const char *, int);
+void pfctl_gateway_kill_states(int, const char *, int);
+void pfctl_label_kill_states(int, const char *, int);
+void pfctl_id_kill_states(int, const char *, int);
+void pfctl_key_kill_states(int, const char *, int);
+int pfctl_parse_host(char *, struct pf_rule_addr *);
+void pfctl_init_options(struct pfctl *);
+int pfctl_load_options(struct pfctl *);
+int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int);
+int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int);
+int pfctl_load_debug(struct pfctl *, unsigned int);
+int pfctl_load_logif(struct pfctl *, char *);
+int pfctl_load_hostid(struct pfctl *, u_int32_t);
+int pfctl_load_reassembly(struct pfctl *, u_int32_t);
+int pfctl_load_syncookies(struct pfctl *, u_int8_t);
+int pfctl_get_pool(int, struct pfctl_pool *, u_int32_t, u_int32_t, int,
+ const char *, int);
+void pfctl_print_eth_rule_counters(struct pfctl_eth_rule *, int);
+void pfctl_print_rule_counters(struct pfctl_rule *, int);
+int pfctl_show_eth_rules(int, char *, int, enum pfctl_show, char *, int, int);
+int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int, int);
+int pfctl_show_nat(int, const char *, int, char *, int, int);
+int pfctl_show_src_nodes(int, int);
+int pfctl_show_states(int, const char *, int);
+int pfctl_show_status(int, int);
+int pfctl_show_running(int);
+int pfctl_show_timeouts(int, int);
+int pfctl_show_limits(int, int);
+void pfctl_read_limits(struct pfctl_handle *);
+void pfctl_restore_limits(void);
+void pfctl_debug(int, u_int32_t, int);
+int pfctl_test_altqsupport(int, int);
+int pfctl_show_anchors(int, int, char *);
+int pfctl_show_eth_anchors(int, int, char *);
+int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *, bool);
+int pfctl_eth_ruleset_trans(struct pfctl *, char *,
+ struct pfctl_eth_anchor *);
+int pfctl_load_eth_ruleset(struct pfctl *, char *,
+ struct pfctl_eth_ruleset *, int);
+int pfctl_load_eth_rule(struct pfctl *, char *, struct pfctl_eth_rule *,
+ int);
+int pfctl_load_ruleset(struct pfctl *, char *,
+ struct pfctl_ruleset *, int, int);
+int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int);
+const char *pfctl_lookup_option(char *, const char * const *);
+void pfctl_reset(int, int);
+int pfctl_walk_show(int, struct pfioc_ruleset *, void *);
+int pfctl_walk_get(int, struct pfioc_ruleset *, void *);
+int pfctl_walk_anchors(int, int, const char *,
+ int(*)(int, struct pfioc_ruleset *, void *), void *);
+struct pfr_anchors *
+ pfctl_get_anchors(int, const char *, int);
+int pfctl_recurse(int, int, const char *,
+ int(*)(int, int, struct pfr_anchoritem *));
+int pfctl_call_clearrules(int, int, struct pfr_anchoritem *);
+int pfctl_call_cleartables(int, int, struct pfr_anchoritem *);
+int pfctl_call_clearanchors(int, int, struct pfr_anchoritem *);
+int pfctl_call_showtables(int, int, struct pfr_anchoritem *);
+
+static struct pfctl_anchor_global pf_anchors;
+struct pfctl_anchor pf_main_anchor;
+struct pfctl_eth_anchor pf_eth_main_anchor;
+static struct pfr_buffer skip_b;
+
+static const char *clearopt;
+static char *rulesopt;
+static const char *showopt;
+static const char *debugopt;
+static char *anchoropt;
+static const char *optiopt = NULL;
+static const char *pf_device = PF_DEVICE;
+static char *ifaceopt;
+static char *tableopt;
+static const char *tblcmdopt;
+static int src_node_killers;
+static char *src_node_kill[2];
+static int state_killers;
+static char *state_kill[2];
+int loadopt;
+int altqsupport;
+
+int dev = -1;
+struct pfctl_handle *pfh = NULL;
+static int first_title = 1;
+static int labels = 0;
+static int exit_val = 0;
+
+#define INDENT(d, o) do { \
+ if (o) { \
+ int i; \
+ for (i=0; i < d; i++) \
+ printf(" "); \
+ } \
+ } while (0); \
+
+
+static const struct {
+ const char *name;
+ int index;
+} pf_limits[] = {
+ { "states", PF_LIMIT_STATES },
+ { "src-nodes", PF_LIMIT_SRC_NODES },
+ { "frags", PF_LIMIT_FRAGS },
+ { "table-entries", PF_LIMIT_TABLE_ENTRIES },
+ { "anchors", PF_LIMIT_ANCHORS },
+ { "eth-anchors", PF_LIMIT_ETH_ANCHORS },
+ { NULL, 0 }
+};
+
+static unsigned int limit_curr[PF_LIMIT_MAX];
+
+struct pf_hint {
+ const char *name;
+ int timeout;
+};
+static const struct pf_hint pf_hint_normal[] = {
+ { "tcp.first", 2 * 60 },
+ { "tcp.opening", 30 },
+ { "tcp.established", 24 * 60 * 60 },
+ { "tcp.closing", 15 * 60 },
+ { "tcp.finwait", 45 },
+ { "tcp.closed", 90 },
+ { "tcp.tsdiff", 30 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_satellite[] = {
+ { "tcp.first", 3 * 60 },
+ { "tcp.opening", 30 + 5 },
+ { "tcp.established", 24 * 60 * 60 },
+ { "tcp.closing", 15 * 60 + 5 },
+ { "tcp.finwait", 45 + 5 },
+ { "tcp.closed", 90 + 5 },
+ { "tcp.tsdiff", 60 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_conservative[] = {
+ { "tcp.first", 60 * 60 },
+ { "tcp.opening", 15 * 60 },
+ { "tcp.established", 5 * 24 * 60 * 60 },
+ { "tcp.closing", 60 * 60 },
+ { "tcp.finwait", 10 * 60 },
+ { "tcp.closed", 3 * 60 },
+ { "tcp.tsdiff", 60 },
+ { NULL, 0 }
+};
+static const struct pf_hint pf_hint_aggressive[] = {
+ { "tcp.first", 30 },
+ { "tcp.opening", 5 },
+ { "tcp.established", 5 * 60 * 60 },
+ { "tcp.closing", 60 },
+ { "tcp.finwait", 30 },
+ { "tcp.closed", 30 },
+ { "tcp.tsdiff", 10 },
+ { NULL, 0 }
+};
+
+static const struct {
+ const char *name;
+ const struct pf_hint *hint;
+} pf_hints[] = {
+ { "normal", pf_hint_normal },
+ { "satellite", pf_hint_satellite },
+ { "high-latency", pf_hint_satellite },
+ { "conservative", pf_hint_conservative },
+ { "aggressive", pf_hint_aggressive },
+ { NULL, NULL }
+};
+
+static const char * const clearopt_list[] = {
+ "nat", "queue", "rules", "Sources",
+ "states", "info", "Tables", "osfp", "all",
+ "ethernet", "Reset", NULL
+};
+
+static const char * const showopt_list[] = {
+ "ether", "nat", "queue", "rules", "Anchors", "Sources", "states",
+ "info", "Interfaces", "labels", "timeouts", "memory", "Tables",
+ "osfp", "Running", "all", "creatorids", NULL
+};
+
+static const char * const tblcmdopt_list[] = {
+ "kill", "flush", "add", "delete", "load", "replace", "show",
+ "test", "zero", "expire", "reset", NULL
+};
+
+static const char * const debugopt_list[] = {
+ "none", "urgent", "misc", "loud", NULL
+};
+
+static const char * const optiopt_list[] = {
+ "none", "basic", "profile", NULL
+};
+
+void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr,
+"usage: %s [-AdeghMmNnOPqRSrvz] [-a anchor] [-D macro=value] [-F modifier]\n"
+ "\t[-f file] [-i interface] [-K host | network]\n"
+ "\t[-k host | network | gateway | label | id] [-o level] [-p device]\n"
+ "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n",
+ __progname);
+
+ exit(1);
+}
+
+void
+pfctl_err(int opts, int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if ((opts & PF_OPT_IGNFAIL) == 0)
+ verr(eval, fmt, ap);
+ else
+ vwarn(fmt, ap);
+
+ va_end(ap);
+
+ exit_val = eval;
+}
+
+void
+pfctl_errx(int opts, int eval, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+
+ if ((opts & PF_OPT_IGNFAIL) == 0)
+ verrx(eval, fmt, ap);
+ else
+ vwarnx(fmt, ap);
+
+ va_end(ap);
+
+ exit_val = eval;
+}
+
+/*
+ * Cache protocol number to name translations.
+ *
+ * Translation is performed a lot e.g., when dumping states and
+ * getprotobynumber is incredibly expensive.
+ *
+ * Note from the getprotobynumber(3) manpage:
+ * <quote>
+ * These functions use a thread-specific data space; if the data is needed
+ * for future use, it should be copied before any subsequent calls overwrite
+ * it. Only the Internet protocols are currently understood.
+ * </quote>
+ *
+ * Consequently we only cache the name and strdup it for safety.
+ *
+ * At the time of writing this comment the last entry in /etc/protocols is:
+ * divert 258 DIVERT # Divert pseudo-protocol [non IANA]
+ */
+const char *
+pfctl_proto2name(int proto)
+{
+ static const char *pfctl_proto_cache[259];
+ struct protoent *p;
+
+ if (proto >= nitems(pfctl_proto_cache)) {
+ p = getprotobynumber(proto);
+ if (p == NULL) {
+ return (NULL);
+ }
+ return (p->p_name);
+ }
+
+ if (pfctl_proto_cache[proto] == NULL) {
+ p = getprotobynumber(proto);
+ if (p == NULL) {
+ return (NULL);
+ }
+ pfctl_proto_cache[proto] = strdup(p->p_name);
+ }
+
+ return (pfctl_proto_cache[proto]);
+}
+
+int
+pfctl_enable(int dev, int opts)
+{
+ int ret;
+
+ if ((ret = pfctl_startstop(pfh, 1)) != 0) {
+ if (ret == EEXIST)
+ errx(1, "pf already enabled");
+ else if (ret == ESRCH)
+ errx(1, "pfil registeration failed");
+ else
+ errc(1, ret, "DIOCSTART");
+ }
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf enabled\n");
+
+ if (altqsupport && ioctl(dev, DIOCSTARTALTQ))
+ if (errno != EEXIST)
+ err(1, "DIOCSTARTALTQ");
+
+ return (0);
+}
+
+int
+pfctl_disable(int dev, int opts)
+{
+ int ret;
+
+ if ((ret = pfctl_startstop(pfh, 0)) != 0) {
+ if (ret == ENOENT)
+ errx(1, "pf not enabled");
+ else
+ errc(1, ret, "DIOCSTOP");
+ }
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf disabled\n");
+
+ if (altqsupport && ioctl(dev, DIOCSTOPALTQ))
+ if (errno != ENOENT)
+ err(1, "DIOCSTOPALTQ");
+
+ return (0);
+}
+
+void
+pfctl_clear_stats(struct pfctl_handle *h, int opts)
+{
+ int ret;
+ if ((ret = pfctl_clear_status(h)) != 0)
+ pfctl_err(opts, 1, "DIOCCLRSTATUS");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf: statistics cleared\n");
+}
+
+void
+pfctl_get_skip_ifaces(void)
+{
+ bzero(&skip_b, sizeof(skip_b));
+ skip_b.pfrb_type = PFRB_IFACES;
+ for (;;) {
+ pfr_buf_grow(&skip_b, skip_b.pfrb_size);
+ skip_b.pfrb_size = skip_b.pfrb_msize;
+ if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size))
+ err(1, "pfi_get_ifaces");
+ if (skip_b.pfrb_size <= skip_b.pfrb_msize)
+ break;
+ }
+}
+
+void
+pfctl_check_skip_ifaces(char *ifname)
+{
+ struct pfi_kif *p;
+ struct node_host *h = NULL, *n = NULL;
+
+ PFRB_FOREACH(p, &skip_b) {
+ if (!strcmp(ifname, p->pfik_name) &&
+ (p->pfik_flags & PFI_IFLAG_SKIP))
+ p->pfik_flags &= ~PFI_IFLAG_SKIP;
+ if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) {
+ if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL)
+ continue;
+
+ for (n = h; n != NULL; n = n->next) {
+ if (strncmp(p->pfik_name, ifname, IFNAMSIZ))
+ continue;
+
+ p->pfik_flags &= ~PFI_IFLAG_SKIP;
+ }
+ }
+ }
+}
+
+void
+pfctl_adjust_skip_ifaces(struct pfctl *pf)
+{
+ struct pfi_kif *p, *pp;
+ struct node_host *h = NULL, *n = NULL;
+
+ PFRB_FOREACH(p, &skip_b) {
+ if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP))
+ continue;
+
+ pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0);
+ if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL)
+ continue;
+
+ for (n = h; n != NULL; n = n->next)
+ PFRB_FOREACH(pp, &skip_b) {
+ if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ))
+ continue;
+
+ if (!(pp->pfik_flags & PFI_IFLAG_SKIP))
+ pfctl_set_interface_flags(pf,
+ pp->pfik_name, PFI_IFLAG_SKIP, 1);
+ if (pp->pfik_flags & PFI_IFLAG_SKIP)
+ pp->pfik_flags &= ~PFI_IFLAG_SKIP;
+ }
+ }
+
+ PFRB_FOREACH(p, &skip_b) {
+ if (! (p->pfik_flags & PFI_IFLAG_SKIP))
+ continue;
+
+ pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0);
+ }
+}
+
+void
+pfctl_clear_interface_flags(int dev, int opts)
+{
+ struct pfioc_iface pi;
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ bzero(&pi, sizeof(pi));
+ pi.pfiio_flags = PFI_IFLAG_SKIP;
+
+ if (ioctl(dev, DIOCCLRIFFLAG, &pi))
+ err(1, "DIOCCLRIFFLAG");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "pf: interface flags reset\n");
+ }
+}
+
+void
+pfctl_flush_eth_rules(int dev, int opts, char *anchorname)
+{
+ int ret;
+
+ ret = pfctl_clear_eth_rules(dev, anchorname);
+ if (ret != 0)
+ err(1, "pfctl_clear_eth_rules");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "Ethernet rules cleared\n");
+}
+
+int
+pfctl_flush_rules(int dev, int opts, char *anchorname)
+{
+ int ret;
+
+ ret = pfctl_clear_rules(dev, anchorname);
+ if (ret != 0) {
+ pfctl_err(opts, 1, "%s", __func__);
+ return (1);
+ } else if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "rules cleared\n");
+
+ return (0);
+}
+
+void
+pfctl_flush_nat(int dev, int opts, char *anchorname)
+{
+ int ret;
+
+ ret = pfctl_clear_nat(dev, anchorname);
+ if (ret != 0)
+ err(1, "pfctl_clear_nat");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "nat cleared\n");
+}
+
+int
+pfctl_clear_altq(int dev, int opts)
+{
+ struct pfr_buffer t;
+
+ if (!altqsupport)
+ return (-1);
+ memset(&t, 0, sizeof(t));
+ t.pfrb_type = PFRB_TRANS;
+ if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") ||
+ pfctl_trans(dev, &t, DIOCXBEGIN, 0) ||
+ pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
+ err(1, "pfctl_clear_altq");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "altq cleared\n");
+ return (0);
+}
+
+void
+pfctl_clear_src_nodes(int dev, int opts)
+{
+ if (ioctl(dev, DIOCCLRSRCNODES))
+ pfctl_err(opts, 1, "DIOCCLRSRCNODES");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "source tracking entries cleared\n");
+}
+
+void
+pfctl_clear_iface_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ unsigned int killed;
+ int ret;
+
+ memset(&kill, 0, sizeof(kill));
+ if (iface != NULL && strlcpy(kill.ifname, iface,
+ sizeof(kill.ifname)) >= sizeof(kill.ifname))
+ pfctl_errx(opts, 1, "invalid interface: %s", iface);
+
+ if (opts & PF_OPT_KILLMATCH)
+ kill.kill_match = true;
+
+ if ((ret = pfctl_clear_states_h(pfh, &kill, &killed)) != 0)
+ pfctl_err(opts, 1, "DIOCCLRSTATUS");
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "%d states cleared\n", killed);
+}
+
+struct addrinfo *
+pfctl_addrprefix(char *addr, struct pf_addr *mask, int numeric)
+{
+ char *p;
+ const char *errstr;
+ int prefix, ret_ga, q, r;
+ struct addrinfo hints, *res;
+
+ bzero(&hints, sizeof(hints));
+ hints.ai_socktype = SOCK_DGRAM; /* dummy */
+ if (numeric)
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if ((p = strchr(addr, '/')) != NULL) {
+ *p++ = '\0';
+ /* prefix only with numeric addresses */
+ hints.ai_flags |= AI_NUMERICHOST;
+ }
+
+ if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) {
+ errx(1, "getaddrinfo: %s", gai_strerror(ret_ga));
+ /* NOTREACHED */
+ }
+
+ if (p == NULL)
+ return (res);
+
+ prefix = strtonum(p, 0, res->ai_family == AF_INET6 ? 128 : 32, &errstr);
+ if (errstr)
+ errx(1, "prefix is %s: %s", errstr, p);
+
+ q = prefix >> 3;
+ r = prefix & 7;
+ switch (res->ai_family) {
+ case AF_INET:
+ bzero(&mask->v4, sizeof(mask->v4));
+ mask->v4.s_addr = htonl((u_int32_t)
+ (0xffffffffffULL << (32 - prefix)));
+ break;
+ case AF_INET6:
+ bzero(&mask->v6, sizeof(mask->v6));
+ if (q > 0)
+ memset((void *)&mask->v6, 0xff, q);
+ if (r > 0)
+ *((u_char *)&mask->v6 + q) =
+ (0xff00 >> r) & 0xff;
+ break;
+ }
+
+ return (res);
+}
+
+void
+pfctl_kill_src_nodes(int dev, int opts)
+{
+ struct pfioc_src_node_kill psnk;
+ struct addrinfo *res[2], *resp[2];
+ struct sockaddr last_src, last_dst;
+ int killed, sources, dests;
+
+ killed = sources = dests = 0;
+
+ memset(&psnk, 0, sizeof(psnk));
+ memset(&psnk.psnk_src.addr.v.a.mask, 0xff,
+ sizeof(psnk.psnk_src.addr.v.a.mask));
+ memset(&last_src, 0xff, sizeof(last_src));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+
+ res[0] = pfctl_addrprefix(src_node_kill[0],
+ &psnk.psnk_src.addr.v.a.mask, (opts & PF_OPT_NODNS));
+
+ for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
+ if (resp[0]->ai_addr == NULL)
+ continue;
+ /* We get lots of duplicates. Catch the easy ones */
+ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
+ continue;
+ last_src = *(struct sockaddr *)resp[0]->ai_addr;
+
+ psnk.psnk_af = resp[0]->ai_family;
+ sources++;
+
+ copy_satopfaddr(&psnk.psnk_src.addr.v.a.addr, resp[0]->ai_addr);
+
+ if (src_node_killers > 1) {
+ dests = 0;
+ memset(&psnk.psnk_dst.addr.v.a.mask, 0xff,
+ sizeof(psnk.psnk_dst.addr.v.a.mask));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ res[1] = pfctl_addrprefix(src_node_kill[1],
+ &psnk.psnk_dst.addr.v.a.mask,
+ (opts & PF_OPT_NODNS));
+ for (resp[1] = res[1]; resp[1];
+ resp[1] = resp[1]->ai_next) {
+ if (resp[1]->ai_addr == NULL)
+ continue;
+ if (psnk.psnk_af != resp[1]->ai_family)
+ continue;
+
+ if (memcmp(&last_dst, resp[1]->ai_addr,
+ sizeof(last_dst)) == 0)
+ continue;
+ last_dst = *(struct sockaddr *)resp[1]->ai_addr;
+
+ dests++;
+
+ copy_satopfaddr(&psnk.psnk_dst.addr.v.a.addr,
+ resp[1]->ai_addr);
+ if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
+ err(1, "DIOCKILLSRCNODES");
+ killed += psnk.psnk_killed;
+ }
+ freeaddrinfo(res[1]);
+ } else {
+ if (ioctl(dev, DIOCKILLSRCNODES, &psnk))
+ err(1, "DIOCKILLSRCNODES");
+ killed += psnk.psnk_killed;
+ }
+ }
+
+ freeaddrinfo(res[0]);
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d src nodes from %d sources and %d "
+ "destinations\n", killed, sources, dests);
+}
+
+void
+pfctl_net_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ struct addrinfo *res[2], *resp[2];
+ struct sockaddr last_src, last_dst;
+ unsigned int newkilled;
+ int killed, sources, dests;
+ int ret;
+
+ killed = sources = dests = 0;
+
+ memset(&kill, 0, sizeof(kill));
+ memset(&kill.src.addr.v.a.mask, 0xff,
+ sizeof(kill.src.addr.v.a.mask));
+ memset(&last_src, 0xff, sizeof(last_src));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ if (iface != NULL && strlcpy(kill.ifname, iface,
+ sizeof(kill.ifname)) >= sizeof(kill.ifname))
+ pfctl_errx(opts, 1, "invalid interface: %s", iface);
+
+ if (state_killers == 2 && (strcmp(state_kill[0], "nat") == 0)) {
+ kill.nat = true;
+ state_kill[0] = state_kill[1];
+ state_killers = 1;
+ }
+
+ res[0] = pfctl_addrprefix(state_kill[0],
+ &kill.src.addr.v.a.mask, (opts & PF_OPT_NODNS));
+
+ if (opts & PF_OPT_KILLMATCH)
+ kill.kill_match = true;
+
+ for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) {
+ if (resp[0]->ai_addr == NULL)
+ continue;
+ /* We get lots of duplicates. Catch the easy ones */
+ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0)
+ continue;
+ last_src = *(struct sockaddr *)resp[0]->ai_addr;
+
+ kill.af = resp[0]->ai_family;
+ sources++;
+
+ copy_satopfaddr(&kill.src.addr.v.a.addr, resp[0]->ai_addr);
+
+ if (state_killers > 1) {
+ dests = 0;
+ memset(&kill.dst.addr.v.a.mask, 0xff,
+ sizeof(kill.dst.addr.v.a.mask));
+ memset(&last_dst, 0xff, sizeof(last_dst));
+ res[1] = pfctl_addrprefix(state_kill[1],
+ &kill.dst.addr.v.a.mask,
+ (opts & PF_OPT_NODNS));
+ for (resp[1] = res[1]; resp[1];
+ resp[1] = resp[1]->ai_next) {
+ if (resp[1]->ai_addr == NULL)
+ continue;
+ if (kill.af != resp[1]->ai_family)
+ continue;
+
+ if (memcmp(&last_dst, resp[1]->ai_addr,
+ sizeof(last_dst)) == 0)
+ continue;
+ last_dst = *(struct sockaddr *)resp[1]->ai_addr;
+
+ dests++;
+
+ copy_satopfaddr(&kill.dst.addr.v.a.addr,
+ resp[1]->ai_addr);
+
+ if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0)
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+ killed += newkilled;
+ }
+ freeaddrinfo(res[1]);
+ } else {
+ if ((ret = pfctl_kill_states_h(pfh, &kill, &newkilled)) != 0)
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+ killed += newkilled;
+ }
+ }
+
+ freeaddrinfo(res[0]);
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states from %d sources and %d "
+ "destinations\n", killed, sources, dests);
+}
+
+void
+pfctl_gateway_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ struct addrinfo *res, *resp;
+ struct sockaddr last_src;
+ unsigned int newkilled;
+ int killed = 0;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no gateway specified");
+ usage();
+ }
+
+ memset(&kill, 0, sizeof(kill));
+ memset(&kill.rt_addr.addr.v.a.mask, 0xff,
+ sizeof(kill.rt_addr.addr.v.a.mask));
+ memset(&last_src, 0xff, sizeof(last_src));
+ if (iface != NULL && strlcpy(kill.ifname, iface,
+ sizeof(kill.ifname)) >= sizeof(kill.ifname))
+ pfctl_errx(opts, 1, "invalid interface: %s", iface);
+
+ if (opts & PF_OPT_KILLMATCH)
+ kill.kill_match = true;
+
+ res = pfctl_addrprefix(state_kill[1], &kill.rt_addr.addr.v.a.mask,
+ (opts & PF_OPT_NODNS));
+
+ for (resp = res; resp; resp = resp->ai_next) {
+ if (resp->ai_addr == NULL)
+ continue;
+ /* We get lots of duplicates. Catch the easy ones */
+ if (memcmp(&last_src, resp->ai_addr, sizeof(last_src)) == 0)
+ continue;
+ last_src = *(struct sockaddr *)resp->ai_addr;
+
+ kill.af = resp->ai_family;
+
+ copy_satopfaddr(&kill.rt_addr.addr.v.a.addr,
+ resp->ai_addr);
+ if (pfctl_kill_states_h(pfh, &kill, &newkilled))
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+ killed += newkilled;
+ }
+
+ freeaddrinfo(res);
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", killed);
+}
+
+void
+pfctl_label_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ unsigned int killed;
+ int ret;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no label specified");
+ usage();
+ }
+ memset(&kill, 0, sizeof(kill));
+ if (iface != NULL && strlcpy(kill.ifname, iface,
+ sizeof(kill.ifname)) >= sizeof(kill.ifname))
+ pfctl_errx(opts, 1, "invalid interface: %s", iface);
+
+ if (opts & PF_OPT_KILLMATCH)
+ kill.kill_match = true;
+
+ if (strlcpy(kill.label, state_kill[1], sizeof(kill.label)) >=
+ sizeof(kill.label))
+ errx(1, "label too long: %s", state_kill[1]);
+
+ if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", killed);
+}
+
+void
+pfctl_id_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ unsigned int killed;
+ int ret;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no id specified");
+ usage();
+ }
+
+ memset(&kill, 0, sizeof(kill));
+
+ if (opts & PF_OPT_KILLMATCH)
+ kill.kill_match = true;
+
+ if ((sscanf(state_kill[1], "%jx/%x",
+ &kill.cmp.id, &kill.cmp.creatorid)) == 2) {
+ }
+ else if ((sscanf(state_kill[1], "%jx", &kill.cmp.id)) == 1) {
+ kill.cmp.creatorid = 0;
+ } else {
+ warnx("wrong id format specified");
+ usage();
+ }
+ if (kill.cmp.id == 0) {
+ warnx("cannot kill id 0");
+ usage();
+ }
+
+ if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", killed);
+}
+
+void
+pfctl_key_kill_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_kill kill;
+ char *s, *token, *tokens[4];
+ struct protoent *p;
+ u_int i, sidx, didx;
+ int ret, killed;
+
+ if (state_killers != 2 || (strlen(state_kill[1]) == 0)) {
+ warnx("no key specified");
+ usage();
+ }
+ memset(&kill, 0, sizeof(kill));
+
+ if (iface != NULL &&
+ strlcpy(kill.ifname, iface, sizeof(kill.ifname)) >=
+ sizeof(kill.ifname))
+ pfctl_errx(opts, 1, "invalid interface: %s", iface);
+
+ s = strdup(state_kill[1]);
+ if (!s)
+ errx(1, "%s: strdup", __func__);
+ i = 0;
+ while ((token = strsep(&s, " \t")) != NULL)
+ if (*token != '\0') {
+ if (i < 4)
+ tokens[i] = token;
+ i++;
+ }
+ if (i != 4)
+ errx(1, "%s: key must be "
+ "\"protocol host1:port1 direction host2:port2\" format",
+ __func__);
+
+ if ((p = getprotobyname(tokens[0])) == NULL)
+ errx(1, "invalid protocol: %s", tokens[0]);
+ kill.proto = p->p_proto;
+
+ if (strcmp(tokens[2], "->") == 0) {
+ sidx = 1;
+ didx = 3;
+ } else if (strcmp(tokens[2], "<-") == 0) {
+ sidx = 3;
+ didx = 1;
+ } else
+ errx(1, "invalid direction: %s", tokens[2]);
+
+ if (pfctl_parse_host(tokens[sidx], &kill.src) == -1)
+ errx(1, "invalid host: %s", tokens[sidx]);
+ if (pfctl_parse_host(tokens[didx], &kill.dst) == -1)
+ errx(1, "invalid host: %s", tokens[didx]);
+
+ if ((ret = pfctl_kill_states_h(pfh, &kill, &killed)) != 0)
+ pfctl_errx(opts, 1, "DIOCKILLSTATES");
+
+ if ((opts & PF_OPT_QUIET) == 0)
+ fprintf(stderr, "killed %d states\n", killed);
+}
+
+int
+pfctl_parse_host(char *str, struct pf_rule_addr *addr)
+{
+ char *s = NULL, *sbs, *sbe;
+ struct addrinfo hints, *ai;
+
+ s = strdup(str);
+ if (!s)
+ errx(1, "pfctl_parse_host: strdup");
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_DGRAM; /* dummy */
+ hints.ai_flags = AI_NUMERICHOST;
+
+ if ((sbs = strchr(s, '[')) != NULL && (sbe = strrchr(s, ']')) != NULL) {
+ hints.ai_family = AF_INET6;
+ *(sbs++) = *sbe = '\0';
+ } else if ((sbs = strchr(s, ':')) != NULL) {
+ hints.ai_family = AF_INET;
+ *(sbs++) = '\0';
+ } else {
+ /* Assume that no ':<number>' means port 0 */
+ }
+
+ if (getaddrinfo(s, sbs, &hints, &ai) != 0)
+ goto error;
+
+ copy_satopfaddr(&addr->addr.v.a.addr, ai->ai_addr);
+ addr->port[0] = ai->ai_family == AF_INET6 ?
+ ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port :
+ ((struct sockaddr_in *)ai->ai_addr)->sin_port;
+ freeaddrinfo(ai);
+ free(s);
+
+ memset(&addr->addr.v.a.mask, 0xff, sizeof(struct pf_addr));
+ addr->port_op = PF_OP_EQ;
+ addr->addr.type = PF_ADDR_ADDRMASK;
+
+ return (0);
+
+error:
+ free(s);
+ return (-1);
+}
+
+int
+pfctl_get_pool(int dev, struct pfctl_pool *pool, u_int32_t nr,
+ u_int32_t ticket, int r_action, const char *anchorname, int which)
+{
+ struct pfioc_pooladdr pp;
+ struct pfctl_pooladdr *pa;
+ u_int32_t pnr, mpnr;
+ int ret;
+
+ memset(&pp, 0, sizeof(pp));
+ if ((ret = pfctl_get_addrs(pfh, ticket, nr, r_action, anchorname, &mpnr, which)) != 0) {
+ warnc(ret, "DIOCGETADDRS");
+ return (-1);
+ }
+
+ TAILQ_INIT(&pool->list);
+ for (pnr = 0; pnr < mpnr; ++pnr) {
+ if ((ret = pfctl_get_addr(pfh, ticket, nr, r_action, anchorname, pnr, &pp, which)) != 0) {
+ warnc(ret, "DIOCGETADDR");
+ return (-1);
+ }
+ pa = calloc(1, sizeof(struct pfctl_pooladdr));
+ if (pa == NULL)
+ err(1, "calloc");
+ bcopy(&pp.addr, pa, sizeof(struct pfctl_pooladdr));
+ pa->af = pp.af;
+ TAILQ_INSERT_TAIL(&pool->list, pa, entries);
+ }
+
+ return (0);
+}
+
+void
+pfctl_move_pool(struct pfctl_pool *src, struct pfctl_pool *dst)
+{
+ struct pfctl_pooladdr *pa;
+
+ while ((pa = TAILQ_FIRST(&src->list)) != NULL) {
+ TAILQ_REMOVE(&src->list, pa, entries);
+ TAILQ_INSERT_TAIL(&dst->list, pa, entries);
+ }
+}
+
+void
+pfctl_clear_pool(struct pfctl_pool *pool)
+{
+ struct pfctl_pooladdr *pa;
+
+ while ((pa = TAILQ_FIRST(&pool->list)) != NULL) {
+ TAILQ_REMOVE(&pool->list, pa, entries);
+ free(pa);
+ }
+}
+
+void
+pfctl_print_eth_rule_counters(struct pfctl_eth_rule *rule, int opts)
+{
+ if (opts & PF_OPT_VERBOSE) {
+ printf(" [ Evaluations: %-8llu Packets: %-8llu "
+ "Bytes: %-10llu]\n",
+ (unsigned long long)rule->evaluations,
+ (unsigned long long)(rule->packets[0] +
+ rule->packets[1]),
+ (unsigned long long)(rule->bytes[0] +
+ rule->bytes[1]));
+ }
+ if (opts & PF_OPT_VERBOSE2) {
+ char timestr[30];
+
+ if (rule->last_active_timestamp != 0) {
+ bcopy(ctime(&rule->last_active_timestamp), timestr,
+ sizeof(timestr));
+ *strchr(timestr, '\n') = '\0';
+ } else {
+ snprintf(timestr, sizeof(timestr), "N/A");
+ }
+ printf(" [ Last Active Time: %s ]\n", timestr);
+ }
+}
+
+void
+pfctl_print_rule_counters(struct pfctl_rule *rule, int opts)
+{
+ if (opts & PF_OPT_DEBUG) {
+ const char *t[PF_SKIP_COUNT] = { "i", "d", "f",
+ "p", "sa", "da", "sp", "dp" };
+ int i;
+
+ printf(" [ Skip steps: ");
+ for (i = 0; i < PF_SKIP_COUNT; ++i) {
+ if (rule->skip[i].nr == rule->nr + 1)
+ continue;
+ printf("%s=", t[i]);
+ if (rule->skip[i].nr == -1)
+ printf("end ");
+ else
+ printf("%u ", rule->skip[i].nr);
+ }
+ printf("]\n");
+
+ printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n",
+ rule->qname, rule->qid, rule->pqname, rule->pqid);
+ if (rule->rule_flag & PFRULE_EXPIRED)
+ printf(" [ Expired: %lld secs ago ]\n",
+ (long long)(time(NULL) - rule->exptime));
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf(" [ Evaluations: %-8llu Packets: %-8llu "
+ "Bytes: %-10llu States: %-6ju]\n",
+ (unsigned long long)rule->evaluations,
+ (unsigned long long)(rule->packets[0] +
+ rule->packets[1]),
+ (unsigned long long)(rule->bytes[0] +
+ rule->bytes[1]), (uintmax_t)rule->states_cur);
+ printf(" [ Source Nodes: %-6ju "
+ "Limit: %-6ju "
+ "NAT/RDR: %-6ju "
+ "Route: %-6ju "
+ "]\n",
+ (uintmax_t)rule->src_nodes,
+ (uintmax_t)rule->src_nodes_type[PF_SN_LIMIT],
+ (uintmax_t)rule->src_nodes_type[PF_SN_NAT],
+ (uintmax_t)rule->src_nodes_type[PF_SN_ROUTE]);
+ if (!(opts & PF_OPT_DEBUG))
+ printf(" [ Inserted: uid %u pid %u "
+ "State Creations: %-6ju]\n",
+ (unsigned)rule->cuid, (unsigned)rule->cpid,
+ (uintmax_t)rule->states_tot);
+ }
+ if (opts & PF_OPT_VERBOSE2) {
+ char timestr[30];
+ if (rule->last_active_timestamp != 0) {
+ bcopy(ctime(&rule->last_active_timestamp), timestr,
+ sizeof(timestr));
+ *strchr(timestr, '\n') = '\0';
+ } else {
+ snprintf(timestr, sizeof(timestr), "N/A");
+ }
+ printf(" [ Last Active Time: %s ]\n", timestr);
+ }
+}
+
+void
+pfctl_print_title(char *title)
+{
+ if (!first_title)
+ printf("\n");
+ first_title = 0;
+ printf("%s\n", title);
+}
+
+int
+pfctl_show_eth_rules(int dev, char *path, int opts, enum pfctl_show format,
+ char *anchorname, int depth, int wildcard)
+{
+ char anchor_call[MAXPATHLEN];
+ struct pfctl_eth_rules_info info;
+ struct pfctl_eth_rule rule;
+ int brace;
+ int dotitle = opts & PF_OPT_SHOWALL;
+ int len = strlen(path);
+ int ret;
+ char *npath, *p;
+
+ /*
+ * Truncate a trailing / and * on an anchorname before searching for
+ * the ruleset, this is syntactic sugar that doesn't actually make it
+ * to the kernel.
+ */
+ if ((p = strrchr(anchorname, '/')) != NULL &&
+ p[1] == '*' && p[2] == '\0') {
+ p[0] = '\0';
+ }
+
+ if (anchorname[0] == '/') {
+ if ((npath = calloc(1, MAXPATHLEN)) == NULL)
+ errx(1, "calloc");
+ snprintf(npath, MAXPATHLEN, "%s", anchorname);
+ } else {
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname);
+ npath = path;
+ }
+
+ /*
+ * If this anchor was called with a wildcard path, go through
+ * the rulesets in the anchor rather than the rules.
+ */
+ if (wildcard && (opts & PF_OPT_RECURSE)) {
+ struct pfctl_eth_rulesets_info ri;
+ u_int32_t mnr, nr;
+
+ if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, npath)) != 0) {
+ if (ret == EINVAL) {
+ fprintf(stderr, "Anchor '%s' "
+ "not found.\n", anchorname);
+ } else {
+ warnc(ret, "DIOCGETETHRULESETS");
+ return (-1);
+ }
+ }
+ mnr = ri.nr;
+
+ pfctl_print_eth_rule_counters(&rule, opts);
+ for (nr = 0; nr < mnr; ++nr) {
+ struct pfctl_eth_ruleset_info rs;
+
+ if ((ret = pfctl_get_eth_ruleset(dev, npath, nr, &rs)) != 0)
+ errc(1, ret, "DIOCGETETHRULESET");
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("anchor \"%s\" all {\n", rs.name);
+ pfctl_show_eth_rules(dev, npath, opts,
+ format, rs.name, depth + 1, 0);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ path[len] = '\0';
+ return (0);
+ }
+
+ if ((ret = pfctl_get_eth_rules_info(dev, &info, path)) != 0) {
+ warnc(ret, "DIOCGETETHRULES");
+ return (-1);
+ }
+ for (int nr = 0; nr < info.nr; nr++) {
+ brace = 0;
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ if ((ret = pfctl_get_eth_rule(dev, nr, info.ticket, path, &rule,
+ opts & PF_OPT_CLRRULECTRS, anchor_call)) != 0) {
+ warnc(ret, "DIOCGETETHRULE");
+ return (-1);
+ }
+ if (anchor_call[0] &&
+ ((((p = strrchr(anchor_call, '_')) != NULL) &&
+ (p == anchor_call ||
+ *(--p) == '/')) || (opts & PF_OPT_RECURSE))) {
+ brace++;
+ int aclen = strlen(anchor_call);
+ if (anchor_call[aclen - 1] == '*')
+ anchor_call[aclen - 2] = '\0';
+ }
+ p = &anchor_call[0];
+ if (dotitle) {
+ pfctl_print_title("ETH RULES:");
+ dotitle = 0;
+ }
+ print_eth_rule(&rule, anchor_call,
+ opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG));
+ if (brace)
+ printf(" {\n");
+ else
+ printf("\n");
+ pfctl_print_eth_rule_counters(&rule, opts);
+ if (brace) {
+ pfctl_show_eth_rules(dev, path, opts, format,
+ p, depth + 1, rule.anchor_wildcard);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ }
+
+ path[len] = '\0';
+ return (0);
+}
+
+int
+pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format,
+ char *anchorname, int depth, int wildcard)
+{
+ struct pfctl_rules_info ri;
+ struct pfctl_rule rule;
+ char anchor_call[MAXPATHLEN];
+ u_int32_t nr, header = 0;
+ int numeric = opts & PF_OPT_NUMERIC;
+ int len = strlen(path), ret = 0;
+ char *npath, *p;
+
+ /*
+ * Truncate a trailing / and * on an anchorname before searching for
+ * the ruleset, this is syntactic sugar that doesn't actually make it
+ * to the kernel.
+ */
+ if ((p = strrchr(anchorname, '/')) != NULL &&
+ p[1] == '*' && p[2] == '\0') {
+ p[0] = '\0';
+ }
+
+ if (anchorname[0] == '/') {
+ if ((npath = calloc(1, MAXPATHLEN)) == NULL)
+ errx(1, "calloc");
+ strlcpy(npath, anchorname, MAXPATHLEN);
+ } else {
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname);
+ npath = path;
+ }
+
+ /*
+ * If this anchor was called with a wildcard path, go through
+ * the rulesets in the anchor rather than the rules.
+ */
+ if (wildcard && (opts & PF_OPT_RECURSE)) {
+ struct pfioc_ruleset prs;
+ u_int32_t mnr, nr;
+
+ memset(&prs, 0, sizeof(prs));
+ if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0)
+ errx(1, "%s", pf_strerror(ret));
+
+ for (nr = 0; nr < mnr; ++nr) {
+ if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0)
+ errx(1, "%s", pf_strerror(ret));
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("anchor \"%s\" all {\n", prs.name);
+ pfctl_show_rules(dev, npath, opts,
+ format, prs.name, depth + 1, 0);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ path[len] = '\0';
+ return (0);
+ }
+
+ if (opts & PF_OPT_SHOWALL) {
+ ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path);
+ if (ret != 0) {
+ warnx("%s", pf_strerror(ret));
+ goto error;
+ }
+ header++;
+ }
+ ret = pfctl_get_rules_info_h(pfh, &ri, PF_SCRUB, path);
+ if (ret != 0) {
+ warnx("%s", pf_strerror(ret));
+ goto error;
+ }
+ if (opts & PF_OPT_SHOWALL) {
+ if (format == PFCTL_SHOW_RULES && (ri.nr > 0 || header))
+ pfctl_print_title("FILTER RULES:");
+ else if (format == PFCTL_SHOW_LABELS && labels)
+ pfctl_print_title("LABEL COUNTERS:");
+ }
+
+ for (nr = 0; nr < ri.nr; ++nr) {
+ if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_SCRUB,
+ &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) {
+ warnc(ret, "DIOCGETRULENV");
+ goto error;
+ }
+
+ if (pfctl_get_pool(dev, &rule.rdr,
+ nr, ri.ticket, PF_SCRUB, path, PF_RDR) != 0)
+ goto error;
+
+ if (pfctl_get_pool(dev, &rule.nat,
+ nr, ri.ticket, PF_SCRUB, path, PF_NAT) != 0)
+ goto error;
+
+ if (pfctl_get_pool(dev, &rule.route,
+ nr, ri.ticket, PF_SCRUB, path, PF_RT) != 0)
+ goto error;
+
+ switch (format) {
+ case PFCTL_SHOW_LABELS:
+ break;
+ case PFCTL_SHOW_RULES:
+ if (rule.label[0][0] && (opts & PF_OPT_SHOWALL))
+ labels = 1;
+ print_rule(&rule, anchor_call, opts, numeric);
+ /*
+ * Do not print newline, when we have not
+ * printed expired rule.
+ */
+ if (!(rule.rule_flag & PFRULE_EXPIRED) ||
+ (opts & (PF_OPT_VERBOSE2|PF_OPT_DEBUG)))
+ printf("\n");
+ pfctl_print_rule_counters(&rule, opts);
+ break;
+ case PFCTL_SHOW_NOTHING:
+ break;
+ }
+ pfctl_clear_pool(&rule.rdr);
+ pfctl_clear_pool(&rule.nat);
+ pfctl_clear_pool(&rule.route);
+ }
+ ret = pfctl_get_rules_info_h(pfh, &ri, PF_PASS, path);
+ if (ret != 0) {
+ warnc(ret, "DIOCGETRULES");
+ goto error;
+ }
+ for (nr = 0; nr < ri.nr; ++nr) {
+ if ((ret = pfctl_get_clear_rule_h(pfh, nr, ri.ticket, path, PF_PASS,
+ &rule, anchor_call, opts & PF_OPT_CLRRULECTRS)) != 0) {
+ warnc(ret, "DIOCGETRULE");
+ goto error;
+ }
+
+ if (pfctl_get_pool(dev, &rule.rdr,
+ nr, ri.ticket, PF_PASS, path, PF_RDR) != 0)
+ goto error;
+
+ if (pfctl_get_pool(dev, &rule.nat,
+ nr, ri.ticket, PF_PASS, path, PF_NAT) != 0)
+ goto error;
+
+ if (pfctl_get_pool(dev, &rule.route,
+ nr, ri.ticket, PF_PASS, path, PF_RT) != 0)
+ goto error;
+
+ switch (format) {
+ case PFCTL_SHOW_LABELS: {
+ bool show = false;
+ int i = 0;
+
+ while (rule.label[i][0]) {
+ printf("%s ", rule.label[i++]);
+ show = true;
+ }
+
+ if (show) {
+ printf("%llu %llu %llu %llu"
+ " %llu %llu %llu %ju\n",
+ (unsigned long long)rule.evaluations,
+ (unsigned long long)(rule.packets[0] +
+ rule.packets[1]),
+ (unsigned long long)(rule.bytes[0] +
+ rule.bytes[1]),
+ (unsigned long long)rule.packets[0],
+ (unsigned long long)rule.bytes[0],
+ (unsigned long long)rule.packets[1],
+ (unsigned long long)rule.bytes[1],
+ (uintmax_t)rule.states_tot);
+ }
+
+ if (anchor_call[0] &&
+ (((p = strrchr(anchor_call, '/')) ?
+ p[1] == '_' : anchor_call[0] == '_') ||
+ opts & PF_OPT_RECURSE)) {
+ pfctl_show_rules(dev, npath, opts, format,
+ anchor_call, depth, rule.anchor_wildcard);
+ }
+ break;
+ }
+ case PFCTL_SHOW_RULES:
+ if (rule.label[0][0] && (opts & PF_OPT_SHOWALL))
+ labels = 1;
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ print_rule(&rule, anchor_call, opts, numeric);
+
+ /*
+ * If this is a 'unnamed' brace notation
+ * anchor, OR the user has explicitly requested
+ * recursion, print it recursively.
+ */
+ if (anchor_call[0] &&
+ (((p = strrchr(anchor_call, '/')) ?
+ p[1] == '_' : anchor_call[0] == '_') ||
+ opts & PF_OPT_RECURSE)) {
+ printf(" {\n");
+ pfctl_print_rule_counters(&rule, opts);
+ pfctl_show_rules(dev, npath, opts, format,
+ anchor_call, depth + 1,
+ rule.anchor_wildcard);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ } else {
+ printf("\n");
+ pfctl_print_rule_counters(&rule, opts);
+ }
+ break;
+ case PFCTL_SHOW_NOTHING:
+ break;
+ }
+ pfctl_clear_pool(&rule.rdr);
+ pfctl_clear_pool(&rule.nat);
+ }
+
+ error:
+ path[len] = '\0';
+ return (ret);
+}
+
+int
+pfctl_show_nat(int dev, const char *path, int opts, char *anchorname, int depth,
+ int wildcard)
+{
+ struct pfctl_rules_info ri;
+ struct pfctl_rule rule;
+ char anchor_call[MAXPATHLEN];
+ u_int32_t nr;
+ static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT };
+ int i, dotitle = opts & PF_OPT_SHOWALL;
+ int ret;
+ int len = strlen(path);
+ char *npath, *p;
+
+ /*
+ * Truncate a trailing / and * on an anchorname before searching for
+ * the ruleset, this is syntactic sugar that doesn't actually make it
+ * to the kernel.
+ */
+ if ((p = strrchr(anchorname, '/')) != NULL &&
+ p[1] == '*' && p[2] == '\0') {
+ p[0] = '\0';
+ }
+
+ if ((npath = calloc(1, MAXPATHLEN)) == NULL)
+ errx(1, "calloc");
+
+ if (anchorname[0] == '/') {
+ snprintf(npath, MAXPATHLEN, "%s", anchorname);
+ } else {
+ snprintf(npath, MAXPATHLEN, "%s", path);
+ if (npath[0])
+ snprintf(&npath[len], MAXPATHLEN - len, "/%s", anchorname);
+ else
+ snprintf(&npath[len], MAXPATHLEN - len, "%s", anchorname);
+ }
+
+ /*
+ * If this anchor was called with a wildcard path, go through
+ * the rulesets in the anchor rather than the rules.
+ */
+ if (wildcard && (opts & PF_OPT_RECURSE)) {
+ struct pfioc_ruleset prs;
+ u_int32_t mnr, nr;
+ memset(&prs, 0, sizeof(prs));
+ if ((ret = pfctl_get_rulesets(pfh, npath, &mnr)) != 0) {
+ if (ret == EINVAL)
+ fprintf(stderr, "NAT anchor '%s' "
+ "not found.\n", anchorname);
+ else
+ errx(1, "%s", pf_strerror(ret));
+ }
+
+ for (nr = 0; nr < mnr; ++nr) {
+ if ((ret = pfctl_get_ruleset(pfh, npath, nr, &prs)) != 0)
+ errx(1, "%s", pf_strerror(ret));
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("nat-anchor \"%s\" all {\n", prs.name);
+ pfctl_show_nat(dev, npath, opts,
+ prs.name, depth + 1, 0);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ npath[len] = '\0';
+ return (0);
+ }
+
+ for (i = 0; i < 3; i++) {
+ ret = pfctl_get_rules_info_h(pfh, &ri, nattype[i], npath);
+ if (ret != 0) {
+ warnc(ret, "DIOCGETRULES");
+ return (-1);
+ }
+ for (nr = 0; nr < ri.nr; ++nr) {
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+
+ if ((ret = pfctl_get_rule_h(pfh, nr, ri.ticket, npath,
+ nattype[i], &rule, anchor_call)) != 0) {
+ warnc(ret, "DIOCGETRULE");
+ return (-1);
+ }
+ if (pfctl_get_pool(dev, &rule.rdr, nr,
+ ri.ticket, nattype[i], npath, PF_RDR) != 0)
+ return (-1);
+ if (pfctl_get_pool(dev, &rule.nat, nr,
+ ri.ticket, nattype[i], npath, PF_NAT) != 0)
+ return (-1);
+ if (pfctl_get_pool(dev, &rule.route, nr,
+ ri.ticket, nattype[i], npath, PF_RT) != 0)
+ return (-1);
+
+ if (dotitle) {
+ pfctl_print_title("TRANSLATION RULES:");
+ dotitle = 0;
+ }
+ print_rule(&rule, anchor_call,
+ opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC);
+ if (anchor_call[0] &&
+ (((p = strrchr(anchor_call, '/')) ?
+ p[1] == '_' : anchor_call[0] == '_') ||
+ opts & PF_OPT_RECURSE)) {
+ printf(" {\n");
+ pfctl_print_rule_counters(&rule, opts);
+ pfctl_show_nat(dev, npath, opts, anchor_call,
+ depth + 1, rule.anchor_wildcard);
+ INDENT(depth, !(opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ } else {
+ printf("\n");
+ pfctl_print_rule_counters(&rule, opts);
+ }
+ }
+ }
+ return (0);
+}
+
+static int
+pfctl_print_src_node(struct pfctl_src_node *sn, void *arg)
+{
+ int *opts = (int *)arg;
+
+ if (*opts & PF_OPT_SHOWALL) {
+ pfctl_print_title("SOURCE TRACKING NODES:");
+ *opts &= ~PF_OPT_SHOWALL;
+ }
+
+ print_src_node(sn, *opts);
+
+ return (0);
+}
+
+int
+pfctl_show_src_nodes(int dev, int opts)
+{
+ int error;
+
+ error = pfctl_get_srcnodes(pfh, pfctl_print_src_node, &opts);
+
+ return (error);
+}
+
+struct pfctl_show_state_arg {
+ int opts;
+ int dotitle;
+ const char *iface;
+};
+
+static int
+pfctl_show_state(struct pfctl_state *s, void *arg)
+{
+ struct pfctl_show_state_arg *a = (struct pfctl_show_state_arg *)arg;
+
+ if (a->dotitle) {
+ pfctl_print_title("STATES:");
+ a->dotitle = 0;
+ }
+ print_state(s, a->opts);
+
+ return (0);
+}
+
+int
+pfctl_show_states(int dev, const char *iface, int opts)
+{
+ struct pfctl_show_state_arg arg;
+ struct pfctl_state_filter filter = {};
+
+ if (iface != NULL)
+ strlcpy(filter.ifname, iface, IFNAMSIZ);
+
+ arg.opts = opts;
+ arg.dotitle = opts & PF_OPT_SHOWALL;
+ arg.iface = iface;
+
+ if (pfctl_get_filtered_states_iter(&filter, pfctl_show_state, &arg))
+ return (-1);
+
+ return (0);
+}
+
+int
+pfctl_show_status(int dev, int opts)
+{
+ struct pfctl_status *status;
+ struct pfctl_syncookies cookies;
+ int ret;
+
+ if ((status = pfctl_get_status_h(pfh)) == NULL) {
+ warn("DIOCGETSTATUS");
+ return (-1);
+ }
+ if ((ret = pfctl_get_syncookies(dev, &cookies)) != 0) {
+ pfctl_free_status(status);
+ warnc(ret, "DIOCGETSYNCOOKIES");
+ return (-1);
+ }
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("INFO:");
+ print_status(status, &cookies, opts);
+ pfctl_free_status(status);
+ return (0);
+}
+
+int
+pfctl_show_running(int dev)
+{
+ struct pfctl_status *status;
+ int running;
+
+ if ((status = pfctl_get_status_h(pfh)) == NULL) {
+ warn("DIOCGETSTATUS");
+ return (-1);
+ }
+
+ running = status->running;
+
+ print_running(status);
+ pfctl_free_status(status);
+ return (!running);
+}
+
+int
+pfctl_show_timeouts(int dev, int opts)
+{
+ uint32_t seconds;
+ int i;
+ int ret;
+
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("TIMEOUTS:");
+ for (i = 0; pf_timeouts[i].name; i++) {
+ if ((ret = pfctl_get_timeout(pfh, pf_timeouts[i].timeout, &seconds)) != 0)
+ errc(1, ret, "DIOCGETTIMEOUT");
+ printf("%-20s %10d", pf_timeouts[i].name, seconds);
+ if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START &&
+ pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END)
+ printf(" states");
+ else
+ printf("s");
+ printf("\n");
+ }
+ return (0);
+
+}
+
+int
+pfctl_show_limits(int dev, int opts)
+{
+ unsigned int limit;
+ int i;
+ int ret;
+
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("LIMITS:");
+ for (i = 0; pf_limits[i].name; i++) {
+ if ((ret = pfctl_get_limit(pfh, pf_limits[i].index, &limit)) != 0)
+ errc(1, ret, "DIOCGETLIMIT");
+ printf("%-13s ", pf_limits[i].name);
+ if (limit == UINT_MAX)
+ printf("unlimited\n");
+ else
+ printf("hard limit %8u\n", limit);
+ }
+ return (0);
+}
+
+void
+pfctl_read_limits(struct pfctl_handle *h)
+{
+ int i;
+
+ for (i = 0; pf_limits[i].name; i++) {
+ if (pfctl_get_limit(h, i, &limit_curr[i]))
+ err(1, "DIOCGETLIMIT");
+ }
+}
+
+void
+pfctl_restore_limits(void)
+{
+ int i;
+
+ if (pfh == NULL)
+ return;
+
+ for (i = 0; pf_limits[i].name; i++) {
+ if (pfctl_set_limit(pfh, i, limit_curr[i]))
+ warn("DIOCSETLIMIT (%s)", pf_limits[i].name);
+ }
+}
+
+void
+pfctl_show_creators(int opts)
+{
+ int ret;
+ uint32_t creators[16];
+ size_t count = nitems(creators);
+
+ ret = pfctl_get_creatorids(pfh, creators, &count);
+ if (ret != 0)
+ errx(ret, "Failed to retrieve creators");
+
+ printf("Creator IDs:\n");
+ for (size_t i = 0; i < count; i++)
+ printf("%08x\n", creators[i]);
+}
+
+/* callbacks for rule/nat/rdr/addr */
+int
+pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, int which)
+{
+ struct pfctl_pooladdr *pa;
+ int ret;
+
+ TAILQ_FOREACH(pa, &p->list, entries) {
+ memcpy(&pf->paddr.addr, pa, sizeof(struct pfctl_pooladdr));
+ pf->paddr.af = pa->af;
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if ((ret = pfctl_add_addr(pf->h, &pf->paddr, which)) != 0)
+ errc(1, ret, "DIOCADDADDR");
+ }
+ }
+ return (0);
+}
+
+void
+pfctl_init_rule(struct pfctl_rule *r)
+{
+ memset(r, 0, sizeof(struct pfctl_rule));
+ TAILQ_INIT(&(r->rdr.list));
+ TAILQ_INIT(&(r->nat.list));
+ TAILQ_INIT(&(r->route.list));
+}
+
+void
+pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r)
+{
+ u_int8_t rs_num;
+ struct pfctl_rule *rule;
+ struct pfctl_ruleset *rs;
+
+ rs_num = pf_get_ruleset_number(r->action);
+ if (rs_num == PF_RULESET_MAX)
+ errx(1, "Invalid rule type %d", r->action);
+
+ rs = &pf->anchor->ruleset;
+
+ if ((rule = calloc(1, sizeof(*rule))) == NULL)
+ err(1, "calloc");
+ bcopy(r, rule, sizeof(*rule));
+ TAILQ_INIT(&rule->rdr.list);
+ pfctl_move_pool(&r->rdr, &rule->rdr);
+ TAILQ_INIT(&rule->nat.list);
+ pfctl_move_pool(&r->nat, &rule->nat);
+ TAILQ_INIT(&rule->route.list);
+ pfctl_move_pool(&r->route, &rule->route);
+
+ TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries);
+}
+
+int
+pfctl_append_eth_rule(struct pfctl *pf, struct pfctl_eth_rule *r,
+ const char *anchor_call)
+{
+ struct pfctl_eth_rule *rule;
+ struct pfctl_eth_ruleset *rs;
+ char *p;
+
+ rs = &pf->eanchor->ruleset;
+
+ if (anchor_call[0] && r->anchor == NULL) {
+ /*
+ * Don't make non-brace anchors part of the main anchor pool.
+ */
+ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL)
+ err(1, "pfctl_append_rule: calloc");
+
+ pf_init_eth_ruleset(&r->anchor->ruleset);
+ r->anchor->ruleset.anchor = r->anchor;
+ if (strlcpy(r->anchor->path, anchor_call,
+ sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path))
+ errx(1, "pfctl_append_rule: strlcpy");
+ if ((p = strrchr(anchor_call, '/')) != NULL) {
+ if (!strlen(p))
+ err(1, "pfctl_append_eth_rule: bad anchor name %s",
+ anchor_call);
+ } else
+ p = (char *)anchor_call;
+ if (strlcpy(r->anchor->name, p,
+ sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name))
+ errx(1, "pfctl_append_eth_rule: strlcpy");
+ }
+
+ if ((rule = calloc(1, sizeof(*rule))) == NULL)
+ err(1, "calloc");
+ bcopy(r, rule, sizeof(*rule));
+
+ TAILQ_INSERT_TAIL(&rs->rules, rule, entries);
+ return (0);
+}
+
+int
+pfctl_eth_ruleset_trans(struct pfctl *pf, char *path,
+ struct pfctl_eth_anchor *a)
+{
+ int osize = pf->trans->pfrb_size;
+
+ if ((pf->loadopt & PFCTL_FLAG_ETH) != 0) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path))
+ return (1);
+ }
+ if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize))
+ return (5);
+
+ return (0);
+}
+
+int
+pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a, bool do_eth)
+{
+ int osize = pf->trans->pfrb_size;
+
+ if ((pf->loadopt & PFCTL_FLAG_ETH) != 0 && do_eth) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_ETH, path))
+ return (1);
+ }
+ if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_RDR, path))
+ return (1);
+ }
+ if (a == pf->astack[0] && ((altqsupport &&
+ (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path))
+ return (2);
+ }
+ if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) {
+ if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) ||
+ pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path))
+ return (3);
+ }
+ if (pf->loadopt & PFCTL_FLAG_TABLE)
+ if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path))
+ return (4);
+ if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize))
+ return (5);
+
+ return (0);
+}
+
+int
+pfctl_load_eth_ruleset(struct pfctl *pf, char *path,
+ struct pfctl_eth_ruleset *rs, int depth)
+{
+ struct pfctl_eth_rule *r;
+ int error, len = strlen(path);
+ int brace = 0;
+
+ pf->eanchor = rs->anchor;
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->eanchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", pf->eanchor->name);
+
+ if (depth) {
+ if (TAILQ_FIRST(&rs->rules) != NULL) {
+ brace++;
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" {\n");
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ (error = pfctl_eth_ruleset_trans(pf,
+ path, rs->anchor))) {
+ printf("pfctl_load_eth_rulesets: "
+ "pfctl_eth_ruleset_trans %d\n", error);
+ goto error;
+ }
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+ }
+
+ while ((r = TAILQ_FIRST(&rs->rules)) != NULL) {
+ TAILQ_REMOVE(&rs->rules, r, entries);
+
+ error = pfctl_load_eth_rule(pf, path, r, depth);
+ if (error)
+ return (error);
+
+ if (r->anchor) {
+ if ((error = pfctl_load_eth_ruleset(pf, path,
+ &r->anchor->ruleset, depth + 1)))
+ return (error);
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+ free(r);
+ }
+ if (brace && pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ path[len] = '\0';
+
+ return (0);
+error:
+ path[len] = '\0';
+ return (error);
+}
+
+int
+pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r,
+ int depth)
+{
+ char *name;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+ int len = strlen(path);
+ int ret;
+
+ if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor))
+ errx(1, "pfctl_load_eth_rule: strlcpy");
+
+ if (r->anchor) {
+ if (r->anchor->match) {
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len,
+ "/%s", r->anchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len,
+ "%s", r->anchor->name);
+ name = r->anchor->name;
+ } else
+ name = r->anchor->path;
+ } else
+ name = "";
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0)
+ if ((ret = pfctl_add_eth_rule(pf->dev, r, anchor, name,
+ pf->eth_ticket)) != 0)
+ errc(1, ret, "DIOCADDETHRULENV");
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2));
+ print_eth_rule(r, r->anchor ? r->anchor->name : "",
+ pf->opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG));
+ }
+
+ path[len] = '\0';
+
+ return (0);
+}
+
+static int
+pfctl_load_tables(struct pfctl *pf, char *path, struct pfctl_anchor *a,
+ int rs_num)
+{
+ struct pfr_ktable *kt, *ktw;
+ struct pfr_uktable *ukt;
+ char anchor_path[PF_ANCHOR_MAXPATH];
+ int e;
+
+ RB_FOREACH_SAFE(kt, pfr_ktablehead, &pfr_ktables, ktw) {
+ if (strcmp(kt->pfrkt_anchor, a->path) != 0)
+ continue;
+
+ if (path != NULL && *path) {
+ strlcpy(anchor_path, kt->pfrkt_anchor,
+ sizeof(anchor_path));
+ snprintf(kt->pfrkt_anchor, PF_ANCHOR_MAXPATH, "%s/%s",
+ path, anchor_path);
+ }
+ ukt = (struct pfr_uktable *)kt;
+ e = pfr_ina_define(&ukt->pfrukt_t, ukt->pfrukt_addrs.pfrb_caddr,
+ ukt->pfrukt_addrs.pfrb_size, NULL, NULL,
+ pf->anchor->ruleset.tticket,
+ ukt->pfrukt_init_addr ? PFR_FLAG_ADDRSTOO : 0);
+ if (e != 0)
+ err(1, "%s pfr_ina_define() %s@%s", __func__,
+ kt->pfrkt_name, kt->pfrkt_anchor);
+ RB_REMOVE(pfr_ktablehead, &pfr_ktables, kt);
+ pfr_buf_clear(&ukt->pfrukt_addrs);
+ free(ukt);
+ }
+
+ return (0);
+}
+
+int
+pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs,
+ int rs_num, int depth)
+{
+ struct pfctl_rule *r;
+ int error, len = strlen(path);
+ int brace = 0;
+
+ pf->anchor = rs->anchor;
+
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name);
+
+ if (depth) {
+ if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) {
+ brace++;
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf(" {\n");
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ (error = pfctl_ruleset_trans(pf,
+ path, rs->anchor, false))) {
+ printf("%s: "
+ "pfctl_ruleset_trans %d\n", __func__, error);
+ goto error;
+ }
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+ }
+
+ if (pf->optimize && rs_num == PF_RULESET_FILTER)
+ pfctl_optimize_ruleset(pf, rs);
+
+ while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) {
+ TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries);
+
+ for (int i = 0; i < PF_RULE_MAX_LABEL_COUNT; i++)
+ expand_label(r->label[i], PF_RULE_LABEL_SIZE, r);
+ expand_label(r->tagname, PF_TAG_NAME_SIZE, r);
+ expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r);
+
+ if ((error = pfctl_load_rule(pf, path, r, depth)))
+ goto error;
+ if (r->anchor) {
+ if ((error = pfctl_load_ruleset(pf, path,
+ &r->anchor->ruleset, rs_num, depth + 1)))
+ goto error;
+ if ((error = pfctl_load_tables(pf, path, r->anchor, rs_num)))
+ goto error;
+ } else if (pf->opts & PF_OPT_VERBOSE)
+ printf("\n");
+ free(r);
+ }
+ if (brace && pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE));
+ printf("}\n");
+ }
+ path[len] = '\0';
+ return (0);
+
+ error:
+ path[len] = '\0';
+ return (error);
+
+}
+
+int
+pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth)
+{
+ u_int8_t rs_num = pf_get_ruleset_number(r->action);
+ char *name;
+ uint32_t ticket;
+ char anchor[PF_ANCHOR_NAME_SIZE];
+ int len = strlen(path);
+ int error;
+ bool was_present;
+
+ /* set up anchor before adding to path for anchor_call */
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (pf->trans == NULL)
+ errx(1, "pfctl_load_rule: no transaction");
+ ticket = pfctl_get_ticket(pf->trans, rs_num, path);
+ if (rs_num == PF_RULESET_FILTER)
+ pf->anchor->ruleset.tticket = ticket;
+ }
+ if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor))
+ errx(1, "pfctl_load_rule: strlcpy");
+
+ if (r->anchor) {
+ if (r->anchor->match) {
+ if (path[0])
+ snprintf(&path[len], MAXPATHLEN - len,
+ "/%s", r->anchor->name);
+ else
+ snprintf(&path[len], MAXPATHLEN - len,
+ "%s", r->anchor->name);
+ name = r->anchor->name;
+ } else
+ name = r->anchor->path;
+ } else
+ name = "";
+
+ was_present = false;
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if ((error = pfctl_begin_addrs(pf->h,
+ &pf->paddr.ticket)) != 0)
+ errc(1, error, "DIOCBEGINADDRS");
+ }
+
+ if (pfctl_add_pool(pf, &r->rdr, PF_RDR))
+ return (1);
+ if (pfctl_add_pool(pf, &r->nat, PF_NAT))
+ return (1);
+ if (pfctl_add_pool(pf, &r->route, PF_RT))
+ return (1);
+ error = pfctl_add_rule_h(pf->h, r, anchor, name, ticket,
+ pf->paddr.ticket);
+ switch (error) {
+ case 0:
+ /* things worked, do nothing */
+ break;
+ case EEXIST:
+ /* an identical rule is already present */
+ was_present = true;
+ break;
+ default:
+ errc(1, error, "DIOCADDRULE");
+ }
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2));
+ print_rule(r, name,
+ pf->opts & PF_OPT_VERBOSE2,
+ pf->opts & PF_OPT_NUMERIC);
+ if (was_present)
+ printf(" -- rule was already present");
+ }
+ path[len] = '\0';
+ pfctl_clear_pool(&r->rdr);
+ pfctl_clear_pool(&r->nat);
+ return (0);
+}
+
+int
+pfctl_add_altq(struct pfctl *pf, struct pf_altq *a)
+{
+ if (altqsupport &&
+ (loadopt & PFCTL_FLAG_ALTQ) != 0) {
+ memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq));
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) {
+ if (errno == ENXIO)
+ errx(1, "qtype not configured");
+ else if (errno == ENODEV)
+ errx(1, "%s: driver does not support "
+ "altq", a->ifname);
+ else
+ err(1, "DIOCADDALTQ");
+ }
+ }
+ pfaltq_store(&pf->paltq->altq);
+ }
+ return (0);
+}
+
+int
+pfctl_rules(int dev, char *filename, int opts, int optimize,
+ char *anchorname, struct pfr_buffer *trans)
+{
+#define ERR(...) do { warn(__VA_ARGS__); goto _error; } while(0)
+#define ERRX(...) do { warnx(__VA_ARGS__); goto _error; } while(0)
+
+ struct pfr_buffer *t, buf;
+ struct pfioc_altq pa;
+ struct pfctl pf;
+ struct pfctl_ruleset *rs;
+ struct pfctl_eth_ruleset *ethrs;
+ struct pfr_table trs;
+ char *path = NULL;
+ int osize;
+
+ RB_INIT(&pf_anchors);
+ memset(&pf_main_anchor, 0, sizeof(pf_main_anchor));
+ pf_init_ruleset(&pf_main_anchor.ruleset);
+ memset(&pf, 0, sizeof(pf));
+ memset(&trs, 0, sizeof(trs));
+ pf_main_anchor.ruleset.anchor = &pf_main_anchor;
+
+ memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor));
+ pf_init_eth_ruleset(&pf_eth_main_anchor.ruleset);
+ pf_eth_main_anchor.ruleset.anchor = &pf_eth_main_anchor;
+
+ if (trans == NULL) {
+ bzero(&buf, sizeof(buf));
+ buf.pfrb_type = PFRB_TRANS;
+ pf.trans = &buf;
+ t = &buf;
+ osize = 0;
+ } else {
+ t = trans;
+ osize = t->pfrb_size;
+ }
+
+ memset(&pa, 0, sizeof(pa));
+ pa.version = PFIOC_ALTQ_VERSION;
+ memset(&pf, 0, sizeof(pf));
+ memset(&trs, 0, sizeof(trs));
+ if ((path = calloc(1, MAXPATHLEN)) == NULL)
+ ERRX("%s: calloc", __func__);
+ if (strlcpy(trs.pfrt_anchor, anchorname,
+ sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor))
+ ERRX("%s: strlcpy", __func__);
+ pf.dev = dev;
+ pf.h = pfh;
+ pf.opts = opts;
+ pf.optimize = optimize;
+ pf.loadopt = loadopt;
+
+ /* non-brace anchor, create without resolving the path */
+ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL)
+ ERRX("%s: calloc", __func__);
+ rs = &pf.anchor->ruleset;
+ pf_init_ruleset(rs);
+ rs->anchor = pf.anchor;
+ if (strlcpy(pf.anchor->path, anchorname,
+ sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(pf.anchor->name, anchorname,
+ sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name))
+ errx(1, "%s: strlcpy", __func__);
+
+
+ pf.astack[0] = pf.anchor;
+ pf.asd = 0;
+ if (anchorname[0])
+ pf.loadopt &= ~PFCTL_FLAG_ALTQ;
+ pf.paltq = &pa;
+ pf.trans = t;
+ pfctl_init_options(&pf);
+
+ /* Set up ethernet anchor */
+ if ((pf.eanchor = calloc(1, sizeof(*pf.eanchor))) == NULL)
+ ERRX("%s: calloc", __func__);
+
+ if (strlcpy(pf.eanchor->path, anchorname,
+ sizeof(pf.eanchor->path)) >= sizeof(pf.eanchor->path))
+ errx(1, "%s: strlcpy", __func__);
+ if (strlcpy(pf.eanchor->name, anchorname,
+ sizeof(pf.eanchor->name)) >= sizeof(pf.eanchor->name))
+ errx(1, "%s: strlcpy", __func__);
+
+ ethrs = &pf.eanchor->ruleset;
+ pf_init_eth_ruleset(ethrs);
+ ethrs->anchor = pf.eanchor;
+ pf.eastack[0] = pf.eanchor;
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ /*
+ * XXX For the time being we need to open transactions for
+ * the main ruleset before parsing, because tables are still
+ * loaded at parse time.
+ */
+ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor, true))
+ ERRX("%s", __func__);
+ if (pf.loadopt & PFCTL_FLAG_ETH)
+ pf.eth_ticket = pfctl_get_ticket(t, PF_RULESET_ETH, anchorname);
+ if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ))
+ pa.ticket =
+ pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname);
+ if (pf.loadopt & PFCTL_FLAG_TABLE)
+ pf.astack[0]->ruleset.tticket =
+ pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname);
+ }
+
+ if (parse_config(filename, &pf) < 0) {
+ if ((opts & PF_OPT_NOACTION) == 0)
+ ERRX("Syntax error in config file: "
+ "pf rules not loaded");
+ else
+ goto _error;
+ }
+ if (loadopt & PFCTL_FLAG_OPTION)
+ pfctl_adjust_skip_ifaces(&pf);
+
+ if ((pf.loadopt & PFCTL_FLAG_FILTER &&
+ (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) ||
+ (pf.loadopt & PFCTL_FLAG_ETH &&
+ (pfctl_load_eth_ruleset(&pf, path, ethrs, 0))) ||
+ (pf.loadopt & PFCTL_FLAG_NAT &&
+ (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) ||
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) ||
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) ||
+ (pf.loadopt & PFCTL_FLAG_FILTER &&
+ pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) {
+ if ((opts & PF_OPT_NOACTION) == 0)
+ ERRX("Unable to load rules into kernel");
+ else
+ goto _error;
+ }
+
+ if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0))
+ if (check_commit_altq(dev, opts) != 0)
+ ERRX("errors in altq config");
+
+ if (trans == NULL) {
+ /* process "load anchor" directives */
+ if (pfctl_load_anchors(dev, &pf) == -1)
+ ERRX("load anchors");
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ if (!anchorname[0] && pfctl_load_options(&pf))
+ goto _error;
+ if (pfctl_trans(dev, t, DIOCXCOMMIT, osize))
+ ERR("DIOCXCOMMIT");
+ }
+ }
+ free(path);
+ return (0);
+
+_error:
+ if (trans == NULL) { /* main ruleset */
+ if ((opts & PF_OPT_NOACTION) == 0)
+ if (pfctl_trans(dev, t, DIOCXROLLBACK, osize))
+ err(1, "DIOCXROLLBACK");
+ exit(1);
+ } else { /* sub ruleset */
+ free(path);
+ return (-1);
+ }
+
+#undef ERR
+#undef ERRX
+}
+
+FILE *
+pfctl_fopen(const char *name, const char *mode)
+{
+ struct stat st;
+ FILE *fp;
+
+ fp = fopen(name, mode);
+ if (fp == NULL)
+ return (NULL);
+ if (fstat(fileno(fp), &st)) {
+ fclose(fp);
+ return (NULL);
+ }
+ if (S_ISDIR(st.st_mode)) {
+ fclose(fp);
+ errno = EISDIR;
+ return (NULL);
+ }
+ return (fp);
+}
+
+void
+pfctl_init_options(struct pfctl *pf)
+{
+
+ pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL;
+ pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
+ pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL;
+ pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL;
+ pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL;
+ pf->timeout[PFTM_SCTP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_SCTP_OPENING] = PFTM_TCP_OPENING_VAL;
+ pf->timeout[PFTM_SCTP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL;
+ pf->timeout[PFTM_SCTP_CLOSING] = PFTM_TCP_CLOSING_VAL;
+ pf->timeout[PFTM_SCTP_CLOSED] = PFTM_TCP_CLOSED_VAL;
+ pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL;
+ pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL;
+ pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL;
+ pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL;
+ pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL;
+ pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL;
+ pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL;
+ pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL;
+ pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL;
+ pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL;
+ pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START;
+ pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END;
+
+ pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT;
+ pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT;
+
+ pf->limit[PF_LIMIT_SRC_NODES] = (limit_curr[PF_LIMIT_SRC_NODES] == 0) ?
+ PFSNODE_HIWAT : limit_curr[PF_LIMIT_SRC_NODES];
+ pf->limit[PF_LIMIT_TABLE_ENTRIES] =
+ (limit_curr[PF_LIMIT_TABLE_ENTRIES] == 0) ?
+ PFR_KENTRY_HIWAT : limit_curr[PF_LIMIT_TABLE_ENTRIES];
+ pf->limit[PF_LIMIT_ANCHORS] = (limit_curr[PF_LIMIT_ANCHORS] == 0) ?
+ PF_ANCHOR_HIWAT : limit_curr[PF_LIMIT_ANCHORS];
+
+ pf->debug = PF_DEBUG_URGENT;
+ pf->reassemble = 0;
+
+ pf->syncookies = false;
+ pf->syncookieswat[0] = PF_SYNCOOKIES_LOWATPCT;
+ pf->syncookieswat[1] = PF_SYNCOOKIES_HIWATPCT;
+}
+
+int
+pfctl_load_options(struct pfctl *pf)
+{
+ int i, error = 0;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ /* load limits */
+ for (i = 0; i < PF_LIMIT_MAX; i++) {
+ if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i])
+ continue;
+ if (pfctl_load_limit(pf, i, pf->limit[i]))
+ error = 1;
+ }
+
+ /*
+ * If we've set the states limit, but haven't explicitly set adaptive
+ * timeouts, do it now with a start of 60% and end of 120%.
+ */
+ if (pf->limit_set[PF_LIMIT_STATES] &&
+ !pf->timeout_set[PFTM_ADAPTIVE_START] &&
+ !pf->timeout_set[PFTM_ADAPTIVE_END]) {
+ pf->timeout[PFTM_ADAPTIVE_START] =
+ (pf->limit[PF_LIMIT_STATES] / 10) * 6;
+ pf->timeout_set[PFTM_ADAPTIVE_START] = 1;
+ pf->timeout[PFTM_ADAPTIVE_END] =
+ (pf->limit[PF_LIMIT_STATES] / 10) * 12;
+ pf->timeout_set[PFTM_ADAPTIVE_END] = 1;
+ }
+
+ /* load timeouts */
+ for (i = 0; i < PFTM_MAX; i++) {
+ if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i])
+ continue;
+ if (pfctl_load_timeout(pf, i, pf->timeout[i]))
+ error = 1;
+ }
+
+ /* load debug */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set)
+ if (pfctl_load_debug(pf, pf->debug))
+ error = 1;
+
+ /* load logif */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set)
+ if (pfctl_load_logif(pf, pf->ifname))
+ error = 1;
+
+ /* load hostid */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set)
+ if (pfctl_load_hostid(pf, pf->hostid))
+ error = 1;
+
+ /* load reassembly settings */
+ if (!(pf->opts & PF_OPT_MERGE) || pf->reass_set)
+ if (pfctl_load_reassembly(pf, pf->reassemble))
+ error = 1;
+
+ /* load keepcounters */
+ if (pfctl_set_keepcounters(pf->dev, pf->keep_counters))
+ error = 1;
+
+ /* load syncookies settings */
+ if (pfctl_load_syncookies(pf, pf->syncookies))
+ error = 1;
+
+ return (error);
+}
+
+int
+pfctl_apply_limit(struct pfctl *pf, const char *opt, unsigned int limit)
+{
+ int i;
+
+
+ for (i = 0; pf_limits[i].name; i++) {
+ if (strcasecmp(opt, pf_limits[i].name) == 0) {
+ pf->limit[pf_limits[i].index] = limit;
+ pf->limit_set[pf_limits[i].index] = 1;
+ break;
+ }
+ }
+ if (pf_limits[i].name == NULL) {
+ warnx("Bad pool name.");
+ return (1);
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set limit %s %d\n", opt, limit);
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0)
+ pfctl_load_options(pf);
+
+ return (0);
+}
+
+int
+pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit)
+{
+ static int restore_limit_handler_armed = 0;
+
+ if (pfctl_set_limit(pf->h, index, limit)) {
+ if (errno == EBUSY)
+ warnx("Current pool size exceeds requested %s limit %u",
+ pf_limits[index].name, limit);
+ else
+ warnx("Cannot set %s limit to %u",
+ pf_limits[index].name, limit);
+ return (1);
+ } else if (restore_limit_handler_armed == 0) {
+ atexit(pfctl_restore_limits);
+ restore_limit_handler_armed = 1;
+ }
+ return (0);
+}
+
+int
+pfctl_apply_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet)
+{
+ int i;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ for (i = 0; pf_timeouts[i].name; i++) {
+ if (strcasecmp(opt, pf_timeouts[i].name) == 0) {
+ pf->timeout[pf_timeouts[i].timeout] = seconds;
+ pf->timeout_set[pf_timeouts[i].timeout] = 1;
+ break;
+ }
+ }
+
+ if (pf_timeouts[i].name == NULL) {
+ warnx("Bad timeout name.");
+ return (1);
+ }
+
+
+ if (pf->opts & PF_OPT_VERBOSE && ! quiet)
+ printf("set timeout %s %d\n", opt, seconds);
+
+ return (0);
+}
+
+int
+pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds)
+{
+ if (pfctl_set_timeout(pf->h, timeout, seconds)) {
+ warnx("DIOCSETTIMEOUT");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_reassembly(struct pfctl *pf, int on, int nodf)
+{
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ pf->reass_set = 1;
+ if (on) {
+ pf->reassemble = PF_REASS_ENABLED;
+ if (nodf)
+ pf->reassemble |= PF_REASS_NODF;
+ } else {
+ pf->reassemble = 0;
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set reassemble %s %s\n", on ? "yes" : "no",
+ nodf ? "no-df" : "");
+
+ return (0);
+}
+
+int
+pfctl_set_optimization(struct pfctl *pf, const char *opt)
+{
+ const struct pf_hint *hint;
+ int i, r;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ for (i = 0; pf_hints[i].name; i++)
+ if (strcasecmp(opt, pf_hints[i].name) == 0)
+ break;
+
+ hint = pf_hints[i].hint;
+ if (hint == NULL) {
+ warnx("invalid state timeouts optimization");
+ return (1);
+ }
+
+ for (i = 0; hint[i].name; i++)
+ if ((r = pfctl_apply_timeout(pf, hint[i].name,
+ hint[i].timeout, 1)))
+ return (r);
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set optimization %s\n", opt);
+
+ return (0);
+}
+
+int
+pfctl_set_logif(struct pfctl *pf, char *ifname)
+{
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ if (!strcmp(ifname, "none")) {
+ free(pf->ifname);
+ pf->ifname = NULL;
+ } else {
+ pf->ifname = strdup(ifname);
+ if (!pf->ifname)
+ errx(1, "pfctl_set_logif: strdup");
+ }
+ pf->ifname_set = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set loginterface %s\n", ifname);
+
+ return (0);
+}
+
+int
+pfctl_load_logif(struct pfctl *pf, char *ifname)
+{
+ if (ifname != NULL && strlen(ifname) >= IFNAMSIZ) {
+ warnx("pfctl_load_logif: strlcpy");
+ return (1);
+ }
+ return (pfctl_set_statusif(pfh, ifname ? ifname : ""));
+}
+
+void
+pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid)
+{
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return;
+
+ HTONL(hostid);
+
+ pf->hostid = hostid;
+ pf->hostid_set = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set hostid 0x%08x\n", ntohl(hostid));
+}
+
+int
+pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid)
+{
+ if (ioctl(dev, DIOCSETHOSTID, &hostid)) {
+ warnx("DIOCSETHOSTID");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_load_reassembly(struct pfctl *pf, u_int32_t reassembly)
+{
+ if (ioctl(dev, DIOCSETREASS, &reassembly)) {
+ warnx("DIOCSETREASS");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_load_syncookies(struct pfctl *pf, u_int8_t val)
+{
+ struct pfctl_syncookies cookies;
+
+ bzero(&cookies, sizeof(cookies));
+
+ cookies.mode = val;
+ cookies.lowwater = pf->syncookieswat[0];
+ cookies.highwater = pf->syncookieswat[1];
+
+ if (pfctl_set_syncookies(dev, &cookies)) {
+ warnx("DIOCSETSYNCOOKIES");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_cfg_syncookies(struct pfctl *pf, uint8_t val, struct pfctl_watermarks *w)
+{
+ if (val != PF_SYNCOOKIES_ADAPTIVE && w != NULL) {
+ warnx("syncookies start/end only apply to adaptive");
+ return (1);
+ }
+ if (val == PF_SYNCOOKIES_ADAPTIVE && w != NULL) {
+ if (!w->hi)
+ w->hi = PF_SYNCOOKIES_HIWATPCT;
+ if (!w->lo)
+ w->lo = w->hi / 2;
+ if (w->lo >= w->hi) {
+ warnx("start must be higher than end");
+ return (1);
+ }
+ pf->syncookieswat[0] = w->lo;
+ pf->syncookieswat[1] = w->hi;
+ pf->syncookieswat_set = 1;
+ }
+
+ if (pf->opts & PF_OPT_VERBOSE) {
+ if (val == PF_SYNCOOKIES_NEVER)
+ printf("set syncookies never\n");
+ else if (val == PF_SYNCOOKIES_ALWAYS)
+ printf("set syncookies always\n");
+ else if (val == PF_SYNCOOKIES_ADAPTIVE) {
+ if (pf->syncookieswat_set)
+ printf("set syncookies adaptive (start %u%%, "
+ "end %u%%)\n", pf->syncookieswat[1],
+ pf->syncookieswat[0]);
+ else
+ printf("set syncookies adaptive\n");
+ } else { /* cannot happen */
+ warnx("king bula ate all syncookies");
+ return (1);
+ }
+ }
+
+ pf->syncookies = val;
+ return (0);
+}
+
+int
+pfctl_do_set_debug(struct pfctl *pf, char *d)
+{
+ u_int32_t level;
+ int ret;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ if (!strcmp(d, "none"))
+ pf->debug = PF_DEBUG_NONE;
+ else if (!strcmp(d, "urgent"))
+ pf->debug = PF_DEBUG_URGENT;
+ else if (!strcmp(d, "misc"))
+ pf->debug = PF_DEBUG_MISC;
+ else if (!strcmp(d, "loud"))
+ pf->debug = PF_DEBUG_NOISY;
+ else {
+ warnx("unknown debug level \"%s\"", d);
+ return (-1);
+ }
+
+ pf->debug_set = 1;
+ level = pf->debug;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0)
+ if ((ret = pfctl_set_debug(pfh, level)) != 0)
+ errc(1, ret, "DIOCSETDEBUG");
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ printf("set debug %s\n", d);
+
+ return (0);
+}
+
+int
+pfctl_load_debug(struct pfctl *pf, unsigned int level)
+{
+ if (pfctl_set_debug(pf->h, level)) {
+ warnx("DIOCSETDEBUG");
+ return (1);
+ }
+ return (0);
+}
+
+int
+pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how)
+{
+ struct pfioc_iface pi;
+ struct node_host *h = NULL, *n = NULL;
+
+ if ((loadopt & PFCTL_FLAG_OPTION) == 0)
+ return (0);
+
+ bzero(&pi, sizeof(pi));
+
+ pi.pfiio_flags = flags;
+
+ /* Make sure our cache matches the kernel. If we set or clear the flag
+ * for a group this applies to all members. */
+ h = ifa_grouplookup(ifname, 0);
+ for (n = h; n != NULL; n = n->next)
+ pfctl_set_interface_flags(pf, n->ifname, flags, how);
+
+ if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >=
+ sizeof(pi.pfiio_name))
+ errx(1, "pfctl_set_interface_flags: strlcpy");
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0) {
+ if (how == 0) {
+ if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi))
+ pfctl_err(pf->opts, 1, "DIOCCLRIFFLAG");
+ } else {
+ if (ioctl(pf->dev, DIOCSETIFFLAG, &pi))
+ err(1, "DIOCSETIFFLAG");
+ pfctl_check_skip_ifaces(ifname);
+ }
+ }
+ return (0);
+}
+
+void
+pfctl_debug(int dev, u_int32_t level, int opts)
+{
+ int ret;
+
+ if ((ret = pfctl_set_debug(pfh, level)) != 0)
+ errc(1, ret, "DIOCSETDEBUG");
+ if ((opts & PF_OPT_QUIET) == 0) {
+ fprintf(stderr, "debug level set to '");
+ switch (level) {
+ case PF_DEBUG_NONE:
+ fprintf(stderr, "none");
+ break;
+ case PF_DEBUG_URGENT:
+ fprintf(stderr, "urgent");
+ break;
+ case PF_DEBUG_MISC:
+ fprintf(stderr, "misc");
+ break;
+ case PF_DEBUG_NOISY:
+ fprintf(stderr, "loud");
+ break;
+ default:
+ fprintf(stderr, "<invalid>");
+ break;
+ }
+ fprintf(stderr, "'\n");
+ }
+}
+
+int
+pfctl_test_altqsupport(int dev, int opts)
+{
+ struct pfioc_altq pa;
+
+ pa.version = PFIOC_ALTQ_VERSION;
+ if (ioctl(dev, DIOCGETALTQS, &pa)) {
+ if (errno == ENODEV) {
+ if (opts & PF_OPT_VERBOSE)
+ fprintf(stderr, "No ALTQ support in kernel\n"
+ "ALTQ related functions disabled\n");
+ return (0);
+ } else
+ err(1, "DIOCGETALTQS");
+ }
+ return (1);
+}
+
+int
+pfctl_walk_show(int opts, struct pfioc_ruleset *pr, void *warg)
+{
+ if (pr->path[0]) {
+ if (pr->path[0] != '_' || (opts & PF_OPT_VERBOSE))
+ printf(" %s/%s\n", pr->path, pr->name);
+ } else if (pr->name[0] != '_' || (opts & PF_OPT_VERBOSE))
+ printf(" %s\n", pr->name);
+
+ return (0);
+}
+
+int
+pfctl_walk_get(int opts, struct pfioc_ruleset *pr, void *warg)
+{
+ struct pfr_anchoritem *pfra;
+ struct pfr_anchors *anchors;
+ int e;
+
+ anchors = (struct pfr_anchors *)warg;
+
+ pfra = malloc(sizeof(*pfra));
+ if (pfra == NULL)
+ err(1, "%s", __func__);
+
+ if (pr->path[0])
+ e = asprintf(&pfra->pfra_anchorname, "%s/%s", pr->path,
+ pr->name);
+ else
+ e = asprintf(&pfra->pfra_anchorname, "%s", pr->name);
+
+ if (e == -1)
+ err(1, "%s", __func__);
+
+ SLIST_INSERT_HEAD(anchors, pfra, pfra_sle);
+
+ return (0);
+}
+
+int
+pfctl_walk_anchors(int dev, int opts, const char *anchor,
+ int(walkf)(int, struct pfioc_ruleset *, void *), void *warg)
+{
+ struct pfioc_ruleset pr;
+ u_int32_t mnr, nr;
+ int ret;
+
+ memset(&pr, 0, sizeof(pr));
+ if ((ret = pfctl_get_rulesets(pfh, anchor, &mnr)) != 0)
+ errx(1, "%s", pf_strerror(ret));
+ for (nr = 0; nr < mnr; ++nr) {
+ char sub[MAXPATHLEN];
+
+ if ((ret = pfctl_get_ruleset(pfh, anchor, nr, &pr)) != 0)
+ errc(1, ret, "DIOCGETRULESET");
+ if (!strcmp(pr.name, PF_RESERVED_ANCHOR))
+ continue;
+ sub[0] = '\0';
+ if (walkf(opts, &pr, warg))
+ return (-1);
+
+ if (pr.path[0])
+ snprintf(sub, sizeof(sub), "%s/%s", pr.path, pr.name);
+ else
+ snprintf(sub, sizeof(sub), "%s", pr.name);
+ if (pfctl_walk_anchors(dev, opts, sub, walkf, warg))
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pfctl_show_anchors(int dev, int opts, char *anchor)
+{
+ return (
+ pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_show, NULL));
+}
+
+struct pfr_anchors *
+pfctl_get_anchors(int dev, const char *anchor, int opts)
+{
+ struct pfioc_ruleset pr;
+ static struct pfr_anchors anchors;
+ char anchorbuf[PATH_MAX];
+ char *n;
+
+ SLIST_INIT(&anchors);
+
+ memset(&pr, 0, sizeof(pr));
+ if (*anchor != '\0') {
+ strlcpy(anchorbuf, anchor, sizeof(anchorbuf));
+ n = dirname(anchorbuf);
+ if (n[0] != '.' && n[1] != '\0')
+ strlcpy(pr.path, n, sizeof(pr.path));
+ strlcpy(anchorbuf, anchor, sizeof(anchorbuf));
+ n = basename(anchorbuf);
+ if (n != NULL)
+ strlcpy(pr.name, n, sizeof(pr.name));
+ }
+
+ /* insert a root anchor first. */
+ pfctl_walk_get(opts, &pr, &anchors);
+
+ if (pfctl_walk_anchors(dev, opts, anchor, pfctl_walk_get, &anchors))
+ errx(1, "%s failed to retrieve list of anchors, can't continue",
+ __func__);
+
+ return (&anchors);
+}
+
+int
+pfctl_call_cleartables(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+ /*
+ * PF_OPT_QUIET makes pfctl_clear_tables() to stop printing number of
+ * tables cleared for given anchor.
+ */
+ opts |= PF_OPT_QUIET;
+ return ((pfctl_do_clear_tables(pfra->pfra_anchorname, opts) == -1) ?
+ 1 : 0);
+}
+
+int
+pfctl_call_clearrules(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+ /*
+ * PF_OPT_QUIET makes pfctl_clear_rules() to stop printing a 'rules
+ * cleared' message for every anchor it deletes.
+ */
+ opts |= PF_OPT_QUIET;
+ return (pfctl_flush_rules(dev, opts, pfra->pfra_anchorname));
+}
+
+int
+pfctl_call_clearanchors(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+ int rv = 0;
+
+ rv |= pfctl_call_cleartables(dev, opts, pfra);
+ rv |= pfctl_call_clearrules(dev, opts, pfra);
+
+ return (rv);
+}
+
+int
+pfctl_call_showtables(int dev, int opts, struct pfr_anchoritem *pfra)
+{
+ pfctl_show_tables(pfra->pfra_anchorname, opts);
+ return (0);
+}
+
+int
+pfctl_recurse(int dev, int opts, const char *anchorname,
+ int(*walkf)(int, int, struct pfr_anchoritem *))
+{
+ int rv = 0;
+ struct pfr_anchors *anchors;
+ struct pfr_anchoritem *pfra, *pfra_save;
+
+ anchors = pfctl_get_anchors(dev, anchorname, opts);
+ /*
+ * While traversing the list, pfctl_clear_*() must always return
+ * so that failures on one anchor do not prevent clearing others.
+ */
+ opts |= PF_OPT_IGNFAIL;
+ if ((opts & PF_OPT_CALLSHOW) == 0)
+ printf("Removing:\n");
+ SLIST_FOREACH_SAFE(pfra, anchors, pfra_sle, pfra_save) {
+ if ((opts & PF_OPT_CALLSHOW) == 0)
+ printf(" %s\n",
+ (*pfra->pfra_anchorname == '\0') ? "/" :
+ pfra->pfra_anchorname);
+ rv |= walkf(dev, opts, pfra);
+ SLIST_REMOVE(anchors, pfra, pfr_anchoritem, pfra_sle);
+ free(pfra->pfra_anchorname);
+ free(pfra);
+ }
+
+ return (rv);
+}
+
+int
+pfctl_show_eth_anchors(int dev, int opts, char *anchorname)
+{
+ struct pfctl_eth_rulesets_info ri;
+ struct pfctl_eth_ruleset_info rs;
+ int ret;
+
+ if ((ret = pfctl_get_eth_rulesets_info(dev, &ri, anchorname)) != 0) {
+ if (ret != ENOENT)
+ errc(1, ret, "DIOCGETETHRULESETS");
+ return (-1);
+ }
+
+ for (int nr = 0; nr < ri.nr; nr++) {
+ char sub[MAXPATHLEN];
+
+ if ((ret = pfctl_get_eth_ruleset(dev, anchorname, nr, &rs)) != 0)
+ errc(1, ret, "DIOCGETETHRULESET");
+
+ if (!strcmp(rs.name, PF_RESERVED_ANCHOR))
+ continue;
+ sub[0] = 0;
+ if (rs.path[0]) {
+ strlcat(sub, rs.path, sizeof(sub));
+ strlcat(sub, "/", sizeof(sub));
+ }
+ strlcat(sub, rs.name, sizeof(sub));
+ if (sub[0] != '_' || (opts & PF_OPT_VERBOSE))
+ printf(" %s\n", sub);
+ if ((opts & PF_OPT_VERBOSE) && pfctl_show_eth_anchors(dev, opts, sub))
+ return (-1);
+ }
+ return (0);
+}
+
+const char *
+pfctl_lookup_option(char *cmd, const char * const *list)
+{
+ if (cmd != NULL && *cmd)
+ for (; *list; list++)
+ if (!strncmp(cmd, *list, strlen(cmd)))
+ return (*list);
+ return (NULL);
+}
+
+void
+pfctl_reset(int dev, int opts)
+{
+ struct pfctl pf;
+ struct pfr_buffer t;
+ int i;
+
+ memset(&pf, 0, sizeof(pf));
+ pf.dev = dev;
+ pf.h = pfh;
+ pfctl_init_options(&pf);
+
+ /* Force reset upon pfctl_load_options() */
+ pf.debug_set = 1;
+ pf.reass_set = 1;
+ pf.syncookieswat_set = 1;
+ pf.ifname = strdup("none");
+ if (pf.ifname == NULL)
+ err(1, "%s: strdup", __func__);
+ pf.ifname_set = 1;
+
+ memset(&t, 0, sizeof(t));
+ t.pfrb_type = PFRB_TRANS;
+ if (pfctl_trans(dev, &t, DIOCXBEGIN, 0))
+ err(1, "%s: DIOCXBEGIN", __func__);
+
+ for (i = 0; pf_limits[i].name; i++)
+ pf.limit_set[pf_limits[i].index] = 1;
+
+ for (i = 0; pf_timeouts[i].name; i++)
+ pf.timeout_set[pf_timeouts[i].timeout] = 1;
+
+ pfctl_load_options(&pf);
+
+ if (pfctl_trans(dev, &t, DIOCXCOMMIT, 0))
+ err(1, "%s: DIOCXCOMMIT", __func__);
+
+ pfctl_clear_interface_flags(dev, opts);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ int mode = O_RDONLY;
+ int opts = 0;
+ int optimize = PF_OPTIMIZE_BASIC;
+ char anchorname[MAXPATHLEN];
+ char *path;
+
+ if (argc < 2)
+ usage();
+
+ while ((ch = getopt(argc, argv,
+ "a:AdD:eqf:F:ghi:k:K:mMnNOo:Pp:rRs:St:T:vx:z")) != -1) {
+ switch (ch) {
+ case 'a':
+ anchoropt = optarg;
+ break;
+ case 'd':
+ opts |= PF_OPT_DISABLE;
+ mode = O_RDWR;
+ break;
+ case 'D':
+ if (pfctl_cmdline_symset(optarg) < 0)
+ warnx("could not parse macro definition %s",
+ optarg);
+ break;
+ case 'e':
+ opts |= PF_OPT_ENABLE;
+ mode = O_RDWR;
+ break;
+ case 'q':
+ opts |= PF_OPT_QUIET;
+ break;
+ case 'F':
+ clearopt = pfctl_lookup_option(optarg, clearopt_list);
+ if (clearopt == NULL) {
+ warnx("Unknown flush modifier '%s'", optarg);
+ usage();
+ }
+ mode = O_RDWR;
+ break;
+ case 'i':
+ ifaceopt = optarg;
+ break;
+ case 'k':
+ if (state_killers >= 2) {
+ warnx("can only specify -k twice");
+ usage();
+ /* NOTREACHED */
+ }
+ state_kill[state_killers++] = optarg;
+ mode = O_RDWR;
+ break;
+ case 'K':
+ if (src_node_killers >= 2) {
+ warnx("can only specify -K twice");
+ usage();
+ /* NOTREACHED */
+ }
+ src_node_kill[src_node_killers++] = optarg;
+ mode = O_RDWR;
+ break;
+ case 'm':
+ opts |= PF_OPT_MERGE;
+ break;
+ case 'M':
+ opts |= PF_OPT_KILLMATCH;
+ break;
+ case 'n':
+ opts |= PF_OPT_NOACTION;
+ break;
+ case 'N':
+ loadopt |= PFCTL_FLAG_NAT;
+ break;
+ case 'r':
+ opts |= PF_OPT_USEDNS;
+ break;
+ case 'f':
+ rulesopt = optarg;
+ mode = O_RDWR;
+ break;
+ case 'g':
+ opts |= PF_OPT_DEBUG;
+ break;
+ case 'A':
+ loadopt |= PFCTL_FLAG_ALTQ;
+ break;
+ case 'R':
+ loadopt |= PFCTL_FLAG_FILTER;
+ break;
+ case 'o':
+ optiopt = pfctl_lookup_option(optarg, optiopt_list);
+ if (optiopt == NULL) {
+ warnx("Unknown optimization '%s'", optarg);
+ usage();
+ }
+ opts |= PF_OPT_OPTIMIZE;
+ break;
+ case 'O':
+ loadopt |= PFCTL_FLAG_OPTION;
+ break;
+ case 'p':
+ pf_device = optarg;
+ break;
+ case 'P':
+ opts |= PF_OPT_NUMERIC;
+ break;
+ case 's':
+ showopt = pfctl_lookup_option(optarg, showopt_list);
+ if (showopt == NULL) {
+ warnx("Unknown show modifier '%s'", optarg);
+ usage();
+ }
+ break;
+ case 'S':
+ opts |= PF_OPT_NODNS;
+ break;
+ case 't':
+ tableopt = optarg;
+ break;
+ case 'T':
+ tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list);
+ if (tblcmdopt == NULL) {
+ warnx("Unknown table command '%s'", optarg);
+ usage();
+ }
+ break;
+ case 'v':
+ if (opts & PF_OPT_VERBOSE)
+ opts |= PF_OPT_VERBOSE2;
+ opts |= PF_OPT_VERBOSE;
+ break;
+ case 'x':
+ debugopt = pfctl_lookup_option(optarg, debugopt_list);
+ if (debugopt == NULL) {
+ warnx("Unknown debug level '%s'", optarg);
+ usage();
+ }
+ mode = O_RDWR;
+ break;
+ case 'z':
+ opts |= PF_OPT_CLRRULECTRS;
+ mode = O_RDWR;
+ break;
+ case 'h':
+ /* FALLTHROUGH */
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+
+ if ((opts & PF_OPT_NODNS) && (opts & PF_OPT_USEDNS))
+ errx(1, "-N and -r are mutually exclusive");
+
+ if ((tblcmdopt == NULL) ^ (tableopt == NULL))
+ usage();
+
+ if (tblcmdopt != NULL) {
+ argc -= optind;
+ argv += optind;
+ ch = *tblcmdopt;
+ if (ch == 'l') {
+ loadopt |= PFCTL_FLAG_TABLE;
+ tblcmdopt = NULL;
+ } else
+ mode = strchr("st", ch) ? O_RDONLY : O_RDWR;
+ } else if (argc != optind) {
+ warnx("unknown command line argument: %s ...", argv[optind]);
+ usage();
+ /* NOTREACHED */
+ }
+ if (loadopt == 0)
+ loadopt = ~0;
+
+ memset(anchorname, 0, sizeof(anchorname));
+ if (anchoropt != NULL) {
+ int len = strlen(anchoropt);
+
+ if (anchoropt[0] == '\0')
+ errx(1, "anchor name must not be empty");
+ if (mode == O_RDONLY && showopt == NULL && tblcmdopt == NULL) {
+ warnx("anchors apply to -f, -F, -s, and -T only");
+ usage();
+ }
+ if (mode == O_RDWR && tblcmdopt == NULL &&
+ (anchoropt[0] == '_' || strstr(anchoropt, "/_") != NULL))
+ errx(1, "anchor names beginning with '_' cannot "
+ "be modified from the command line");
+
+ if (len >= 1 && anchoropt[len - 1] == '*') {
+ if (len >= 2 && anchoropt[len - 2] == '/')
+ anchoropt[len - 2] = '\0';
+ else
+ anchoropt[len - 1] = '\0';
+ opts |= PF_OPT_RECURSE;
+ }
+ if (strlcpy(anchorname, anchoropt,
+ sizeof(anchorname)) >= sizeof(anchorname))
+ errx(1, "anchor name '%s' too long",
+ anchoropt);
+ loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE|PFCTL_FLAG_ETH;
+ }
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ dev = open(pf_device, mode);
+ if (dev == -1)
+ err(1, "%s", pf_device);
+ altqsupport = pfctl_test_altqsupport(dev, opts);
+ } else {
+ dev = open(pf_device, O_RDONLY);
+ if (dev >= 0)
+ opts |= PF_OPT_DUMMYACTION;
+ /* turn off options */
+ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE);
+ clearopt = showopt = debugopt = NULL;
+#if !defined(ENABLE_ALTQ)
+ altqsupport = 0;
+#else
+ altqsupport = 1;
+#endif
+ }
+ pfh = pfctl_open(pf_device);
+ if (pfh == NULL)
+ err(1, "Failed to open netlink");
+
+ if ((opts & PF_OPT_NOACTION) == 0) {
+ pfctl_read_limits(pfh);
+ }
+
+ if (opts & PF_OPT_DISABLE)
+ if (pfctl_disable(dev, opts))
+ exit_val = 1;
+
+ if ((path = calloc(1, MAXPATHLEN)) == NULL)
+ errx(1, "%s: calloc", __func__);
+
+ if (showopt != NULL) {
+ switch (*showopt) {
+ case 'A':
+ pfctl_show_anchors(dev, opts, anchorname);
+ if (opts & PF_OPT_VERBOSE2)
+ printf("Ethernet:\n");
+ pfctl_show_eth_anchors(dev, opts, anchorname);
+ break;
+ case 'r':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES,
+ anchorname, 0, 0);
+ break;
+ case 'l':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS,
+ anchorname, 0, 0);
+ break;
+ case 'n':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_nat(dev, path, opts, anchorname, 0, 0);
+ break;
+ case 'q':
+ pfctl_show_altq(dev, ifaceopt, opts,
+ opts & PF_OPT_VERBOSE2);
+ break;
+ case 's':
+ pfctl_show_states(dev, ifaceopt, opts);
+ break;
+ case 'S':
+ pfctl_show_src_nodes(dev, opts);
+ break;
+ case 'i':
+ pfctl_show_status(dev, opts);
+ break;
+ case 'R':
+ exit_val = pfctl_show_running(dev);
+ break;
+ case 't':
+ pfctl_show_timeouts(dev, opts);
+ break;
+ case 'm':
+ pfctl_show_limits(dev, opts);
+ break;
+ case 'e':
+ pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0,
+ 0);
+ break;
+ case 'a':
+ opts |= PF_OPT_SHOWALL;
+ pfctl_load_fingerprints(dev, opts);
+
+ pfctl_show_eth_rules(dev, path, opts, 0, anchorname, 0,
+ 0);
+
+ pfctl_show_nat(dev, path, opts, anchorname, 0, 0);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES,
+ anchorname, 0, 0);
+ pfctl_show_altq(dev, ifaceopt, opts, 0);
+ pfctl_show_states(dev, ifaceopt, opts);
+ pfctl_show_src_nodes(dev, opts);
+ pfctl_show_status(dev, opts);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS,
+ anchorname, 0, 0);
+ pfctl_show_timeouts(dev, opts);
+ pfctl_show_limits(dev, opts);
+ pfctl_show_tables(anchorname, opts);
+ pfctl_show_fingerprints(opts);
+ break;
+ case 'T':
+ if (opts & PF_OPT_RECURSE) {
+ opts |= PF_OPT_CALLSHOW;
+ pfctl_recurse(dev, opts, anchorname,
+ pfctl_call_showtables);
+ } else
+ pfctl_show_tables(anchorname, opts);
+ break;
+ case 'o':
+ pfctl_load_fingerprints(dev, opts);
+ pfctl_show_fingerprints(opts);
+ break;
+ case 'I':
+ pfctl_show_ifaces(ifaceopt, opts);
+ break;
+ case 'c':
+ pfctl_show_creators(opts);
+ break;
+ }
+ }
+
+ if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) {
+ pfctl_show_eth_rules(dev, path, opts, PFCTL_SHOW_NOTHING,
+ anchorname, 0, 0);
+ pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING,
+ anchorname, 0, 0);
+ }
+
+ if (clearopt != NULL) {
+ int mnr;
+
+ /* Check if anchor exists. */
+ if ((pfctl_get_rulesets(pfh, anchorname, &mnr)) == ENOENT)
+ errx(1, "No such anchor %s", anchorname);
+
+ switch (*clearopt) {
+ case 'e':
+ pfctl_flush_eth_rules(dev, opts, anchorname);
+ break;
+ case 'r':
+ if (opts & PF_OPT_RECURSE)
+ pfctl_recurse(dev, opts, anchorname,
+ pfctl_call_clearrules);
+ else
+ pfctl_flush_rules(dev, opts, anchorname);
+ break;
+ case 'n':
+ pfctl_flush_nat(dev, opts, anchorname);
+ break;
+ case 'q':
+ pfctl_clear_altq(dev, opts);
+ break;
+ case 's':
+ pfctl_clear_iface_states(dev, ifaceopt, opts);
+ break;
+ case 'S':
+ pfctl_clear_src_nodes(dev, opts);
+ break;
+ case 'i':
+ pfctl_clear_stats(pfh, opts);
+ break;
+ case 'a':
+ if (ifaceopt) {
+ warnx("don't specify an interface with -Fall");
+ usage();
+ /* NOTREACHED */
+ }
+ pfctl_flush_eth_rules(dev, opts, anchorname);
+ pfctl_flush_rules(dev, opts, anchorname);
+ pfctl_flush_nat(dev, opts, anchorname);
+ if (opts & PF_OPT_RECURSE)
+ pfctl_recurse(dev, opts, anchorname,
+ pfctl_call_clearanchors);
+ else {
+ pfctl_do_clear_tables(anchorname, opts);
+ pfctl_flush_rules(dev, opts, anchorname);
+ }
+ if (!*anchorname) {
+ pfctl_clear_altq(dev, opts);
+ pfctl_clear_iface_states(dev, ifaceopt, opts);
+ pfctl_clear_src_nodes(dev, opts);
+ pfctl_clear_stats(pfh, opts);
+ pfctl_clear_fingerprints(dev, opts);
+ pfctl_reset(dev, opts);
+ }
+ break;
+ case 'o':
+ pfctl_clear_fingerprints(dev, opts);
+ break;
+ case 'T':
+ if ((opts & PF_OPT_RECURSE) == 0)
+ pfctl_do_clear_tables(anchorname, opts);
+ else
+ pfctl_recurse(dev, opts, anchorname,
+ pfctl_call_cleartables);
+ break;
+ case 'R':
+ pfctl_reset(dev, opts);
+ break;
+ }
+ }
+ if (state_killers) {
+ if (!strcmp(state_kill[0], "label"))
+ pfctl_label_kill_states(dev, ifaceopt, opts);
+ else if (!strcmp(state_kill[0], "id"))
+ pfctl_id_kill_states(dev, ifaceopt, opts);
+ else if (!strcmp(state_kill[0], "gateway"))
+ pfctl_gateway_kill_states(dev, ifaceopt, opts);
+ else if (!strcmp(state_kill[0], "key"))
+ pfctl_key_kill_states(dev, ifaceopt, opts);
+ else
+ pfctl_net_kill_states(dev, ifaceopt, opts);
+ }
+
+ if (src_node_killers)
+ pfctl_kill_src_nodes(dev, opts);
+
+ if (tblcmdopt != NULL) {
+ exit_val = pfctl_table(argc, argv, tableopt,
+ tblcmdopt, rulesopt, anchorname, opts);
+ rulesopt = NULL;
+ }
+ if (optiopt != NULL) {
+ switch (*optiopt) {
+ case 'n':
+ optimize = 0;
+ break;
+ case 'b':
+ optimize |= PF_OPTIMIZE_BASIC;
+ break;
+ case 'o':
+ case 'p':
+ optimize |= PF_OPTIMIZE_PROFILE;
+ break;
+ }
+ }
+
+ if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) &&
+ !anchorname[0] && !(opts & PF_OPT_NOACTION))
+ pfctl_get_skip_ifaces();
+
+ if (rulesopt != NULL && !(opts & PF_OPT_MERGE) &&
+ !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION))
+ if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE))
+ exit_val = 1;
+
+ if (rulesopt != NULL) {
+ if (pfctl_rules(dev, rulesopt, opts, optimize,
+ anchorname, NULL))
+ exit_val = 1;
+ }
+
+ if (opts & PF_OPT_ENABLE)
+ if (pfctl_enable(dev, opts))
+ exit_val = 1;
+
+ if (debugopt != NULL) {
+ switch (*debugopt) {
+ case 'n':
+ pfctl_debug(dev, PF_DEBUG_NONE, opts);
+ break;
+ case 'u':
+ pfctl_debug(dev, PF_DEBUG_URGENT, opts);
+ break;
+ case 'm':
+ pfctl_debug(dev, PF_DEBUG_MISC, opts);
+ break;
+ case 'l':
+ pfctl_debug(dev, PF_DEBUG_NOISY, opts);
+ break;
+ }
+ }
+
+ /*
+ * prevent pfctl_restore_limits() exit handler from restoring
+ * pf(4) options settings on successful exit.
+ */
+ if (exit_val == 0) {
+ close(dev);
+ dev = -1;
+ pfctl_close(pfh);
+ pfh = NULL;
+ }
+
+ return (exit_val);
+}
+
+char *
+pf_strerror(int errnum)
+{
+ switch (errnum) {
+ case ESRCH:
+ return "Table does not exist.";
+ case EINVAL:
+ case ENOENT:
+ return "Anchor does not exist.";
+ default:
+ return strerror(errnum);
+ }
+}
diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h
new file mode 100644
index 000000000000..c540c6348d84
--- /dev/null
+++ b/sbin/pfctl/pfctl.h
@@ -0,0 +1,202 @@
+/* $OpenBSD: pfctl.h,v 1.42 2007/12/05 12:01:47 chl Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PFCTL_H_
+#define _PFCTL_H_
+
+#include <libpfctl.h>
+
+#ifdef PFCTL_DEBUG
+#define DBGPRINT(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define DBGPRINT(...) (void)(0)
+#endif
+
+extern struct pfctl_handle *pfh;
+
+struct pfctl;
+
+enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING };
+
+enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS,
+ PFRB_IFACES, PFRB_TRANS, PFRB_MAX };
+struct pfr_buffer {
+ int pfrb_type; /* type of content, see enum above */
+ int pfrb_size; /* number of objects in buffer */
+ int pfrb_msize; /* maximum number of objects in buffer */
+ void *pfrb_caddr; /* malloc'ated memory area */
+};
+#define PFRB_FOREACH(var, buf) \
+ for ((var) = pfr_buf_next((buf), NULL); \
+ (var) != NULL; \
+ (var) = pfr_buf_next((buf), (var)))
+
+RB_HEAD(pfr_ktablehead, pfr_ktable);
+struct pfr_ktable {
+ struct pfr_tstats pfrkt_ts;
+ RB_ENTRY(pfr_ktable) pfrkt_tree;
+ SLIST_ENTRY(pfr_ktable) pfrkt_workq;
+ struct radix_node_head *pfrkt_ip4;
+ struct radix_node_head *pfrkt_ip6;
+ struct pfr_ktable *pfrkt_shadow;
+ struct pfr_ktable *pfrkt_root;
+ struct pf_kruleset *pfrkt_rs;
+ long pfrkt_larg;
+ int pfrkt_nflags;
+};
+#define pfrkt_t pfrkt_ts.pfrts_t
+#define pfrkt_name pfrkt_t.pfrt_name
+#define pfrkt_anchor pfrkt_t.pfrt_anchor
+#define pfrkt_ruleset pfrkt_t.pfrt_ruleset
+#define pfrkt_flags pfrkt_t.pfrt_flags
+#define pfrkt_cnt pfrkt_kts.pfrkts_cnt
+#define pfrkt_refcnt pfrkt_kts.pfrkts_refcnt
+#define pfrkt_tzero pfrkt_kts.pfrkts_tzero
+
+struct pfr_uktable {
+ struct pfr_ktable pfrukt_kt;
+ struct pfr_buffer pfrukt_addrs;
+ int pfrukt_init_addr;
+ SLIST_ENTRY(pfr_uktable) pfrukt_entry;
+};
+
+#define pfrukt_t pfrukt_kt.pfrkt_ts.pfrts_t
+#define pfrukt_name pfrukt_kt.pfrkt_t.pfrt_name
+#define pfrukt_anchor pfrukt_kt.pfrkt_t.pfrt_anchor
+
+extern struct pfr_ktablehead pfr_ktables;
+
+struct pfr_anchoritem {
+ SLIST_ENTRY(pfr_anchoritem) pfra_sle;
+ char *pfra_anchorname;
+};
+
+SLIST_HEAD(pfr_anchors, pfr_anchoritem);
+
+int pfr_add_table(struct pfr_table *, int *, int);
+int pfr_del_table(struct pfr_table *, int *, int);
+int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int);
+int pfr_clr_astats(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_clr_addrs(struct pfr_table *, int *, int);
+int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int,
+ int *, int *, int *, int);
+int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int);
+int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int);
+int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int);
+int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *,
+ int *, int, int);
+void pfr_buf_clear(struct pfr_buffer *);
+int pfr_buf_add(struct pfr_buffer *, const void *);
+void *pfr_buf_next(struct pfr_buffer *, const void *);
+int pfr_buf_grow(struct pfr_buffer *, int);
+int pfr_buf_load(struct pfr_buffer *, char *, int,
+ int (*)(struct pfr_buffer *, char *, int, int), int);
+char *pf_strerror(int);
+int pfi_get_ifaces(const char *, struct pfi_kif *, int *);
+
+void pfctl_print_title(char *);
+int pfctl_do_clear_tables(const char *, int);
+void pfctl_show_tables(const char *, int);
+int pfctl_table(int, char *[], char *, const char *, char *,
+ const char *, int);
+int pfctl_show_altq(int, const char *, int, int);
+void warn_duplicate_tables(const char *, const char *);
+void pfctl_show_ifaces(const char *, int);
+void pfctl_show_creators(int);
+FILE *pfctl_fopen(const char *, const char *);
+
+#ifdef __FreeBSD__
+extern int altqsupport;
+extern int dummynetsupport;
+#define HTONL(x) (x) = htonl((__uint32_t)(x))
+#endif
+
+#ifndef DEFAULT_PRIORITY
+#define DEFAULT_PRIORITY 1
+#endif
+
+#ifndef DEFAULT_QLIMIT
+#define DEFAULT_QLIMIT 50
+#endif
+
+/*
+ * generalized service curve used for admission control
+ */
+struct segment {
+ LIST_ENTRY(segment) _next;
+ double x, y, d, m;
+};
+
+extern int loadopt;
+
+int check_commit_altq(int, int);
+void pfaltq_store(struct pf_altq *);
+char *rate2str(double);
+
+void print_addr(struct pf_addr_wrap *, sa_family_t, int);
+void print_addr_str(sa_family_t, struct pf_addr *);
+void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int);
+void print_seq(struct pfctl_state_peer *);
+void print_state(struct pfctl_state *, int);
+
+int pfctl_cmdline_symset(char *);
+int pfctl_add_trans(struct pfr_buffer *, int, const char *);
+u_int32_t
+ pfctl_get_ticket(struct pfr_buffer *, int, const char *);
+int pfctl_trans(int, struct pfr_buffer *, u_long, int);
+
+int pf_get_ruleset_number(u_int8_t);
+void pf_init_ruleset(struct pfctl_ruleset *);
+int pfctl_anchor_setup(struct pfctl_rule *,
+ const struct pfctl_ruleset *, const char *);
+void pf_remove_if_empty_ruleset(struct pfctl_ruleset *);
+struct pfctl_ruleset *pf_find_ruleset(const char *);
+struct pfctl_ruleset *pf_find_or_create_ruleset(const char *);
+void pf_init_eth_ruleset(struct pfctl_eth_ruleset *);
+int pfctl_eth_anchor_setup(struct pfctl *,
+ struct pfctl_eth_rule *,
+ const struct pfctl_eth_ruleset *, const char *);
+struct pfctl_eth_ruleset *pf_find_or_create_eth_ruleset(const char *);
+void pf_remove_if_empty_eth_ruleset(
+ struct pfctl_eth_ruleset *);
+
+void expand_label(char *, size_t, struct pfctl_rule *);
+
+const char *pfctl_proto2name(int);
+
+void pfctl_err(int, int, const char *, ...);
+void pfctl_errx(int, int, const char *, ...);
+
+#endif /* _PFCTL_H_ */
diff --git a/sbin/pfctl/pfctl_altq.c b/sbin/pfctl/pfctl_altq.c
new file mode 100644
index 000000000000..1b32e90aea10
--- /dev/null
+++ b/sbin/pfctl/pfctl_altq.c
@@ -0,0 +1,1437 @@
+/* $OpenBSD: pfctl_altq.c,v 1.93 2007/10/15 02:16:35 deraadt Exp $ */
+
+/*
+ * Copyright (c) 2002
+ * Sony Computer Science Laboratories Inc.
+ * Copyright (c) 2002, 2003 Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+#define PFIOC_USE_LATEST
+#define _WANT_FREEBSD_BITSET
+
+#include <sys/types.h>
+#include <sys/bitset.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+
+#include <err.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <math.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_codel.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+#include <net/altq/altq_fairq.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+#define is_sc_null(sc) (((sc) == NULL) || ((sc)->m1 == 0 && (sc)->m2 == 0))
+
+static STAILQ_HEAD(interfaces, pfctl_altq) interfaces = STAILQ_HEAD_INITIALIZER(interfaces);
+static struct hsearch_data queue_map;
+static struct hsearch_data if_map;
+static struct hsearch_data qid_map;
+
+static struct pfctl_altq *pfaltq_lookup(char *ifname);
+static struct pfctl_altq *qname_to_pfaltq(const char *, const char *);
+static u_int32_t qname_to_qid(char *);
+
+static int eval_pfqueue_cbq(struct pfctl *, struct pf_altq *,
+ struct pfctl_altq *);
+static int cbq_compute_idletime(struct pfctl *, struct pf_altq *);
+static int check_commit_cbq(int, int, struct pfctl_altq *);
+static int print_cbq_opts(const struct pf_altq *);
+
+static int print_codel_opts(const struct pf_altq *,
+ const struct node_queue_opt *);
+
+static int eval_pfqueue_priq(struct pfctl *, struct pf_altq *,
+ struct pfctl_altq *);
+static int check_commit_priq(int, int, struct pfctl_altq *);
+static int print_priq_opts(const struct pf_altq *);
+
+static int eval_pfqueue_hfsc(struct pfctl *, struct pf_altq *,
+ struct pfctl_altq *, struct pfctl_altq *);
+static int check_commit_hfsc(int, int, struct pfctl_altq *);
+static int print_hfsc_opts(const struct pf_altq *,
+ const struct node_queue_opt *);
+
+static int eval_pfqueue_fairq(struct pfctl *, struct pf_altq *,
+ struct pfctl_altq *, struct pfctl_altq *);
+static int print_fairq_opts(const struct pf_altq *,
+ const struct node_queue_opt *);
+static int check_commit_fairq(int, int, struct pfctl_altq *);
+
+static void gsc_add_sc(struct gen_sc *, struct service_curve *);
+static int is_gsc_under_sc(struct gen_sc *,
+ struct service_curve *);
+static struct segment *gsc_getentry(struct gen_sc *, double);
+static int gsc_add_seg(struct gen_sc *, double, double, double,
+ double);
+static double sc_x2y(struct service_curve *, double);
+
+u_int32_t getifspeed(char *);
+u_long getifmtu(char *);
+int eval_queue_opts(struct pf_altq *, struct node_queue_opt *,
+ u_int64_t);
+u_int64_t eval_bwspec(struct node_queue_bw *, u_int64_t);
+void print_hfsc_sc(const char *, u_int, u_int, u_int,
+ const struct node_hfsc_sc *);
+void print_fairq_sc(const char *, u_int, u_int, u_int,
+ const struct node_fairq_sc *);
+
+static __attribute__((constructor)) void
+pfctl_altq_init(void)
+{
+ /*
+ * As hdestroy() will never be called on these tables, it will be
+ * safe to use references into the stored data as keys.
+ */
+ if (hcreate_r(0, &queue_map) == 0)
+ err(1, "Failed to create altq queue map");
+ if (hcreate_r(0, &if_map) == 0)
+ err(1, "Failed to create altq interface map");
+ if (hcreate_r(0, &qid_map) == 0)
+ err(1, "Failed to create altq queue id map");
+}
+
+void
+pfaltq_store(struct pf_altq *a)
+{
+ struct pfctl_altq *altq;
+ ENTRY item;
+ ENTRY *ret_item;
+ size_t key_size;
+
+ if ((altq = malloc(sizeof(*altq))) == NULL)
+ err(1, "queue malloc");
+ memcpy(&altq->pa, a, sizeof(struct pf_altq));
+ memset(&altq->meta, 0, sizeof(altq->meta));
+
+ if (a->qname[0] == 0) {
+ item.key = altq->pa.ifname;
+ item.data = altq;
+ if (hsearch_r(item, ENTER, &ret_item, &if_map) == 0)
+ err(1, "interface map insert");
+ STAILQ_INSERT_TAIL(&interfaces, altq, meta.link);
+ } else {
+ key_size = sizeof(a->ifname) + sizeof(a->qname);
+ if ((item.key = malloc(key_size)) == NULL)
+ err(1, "queue map key malloc");
+ snprintf(item.key, key_size, "%s:%s", a->ifname, a->qname);
+ item.data = altq;
+ if (hsearch_r(item, ENTER, &ret_item, &queue_map) == 0)
+ err(1, "queue map insert");
+
+ item.key = altq->pa.qname;
+ item.data = &altq->pa.qid;
+ if (hsearch_r(item, ENTER, &ret_item, &qid_map) == 0)
+ err(1, "qid map insert");
+ }
+}
+
+static struct pfctl_altq *
+pfaltq_lookup(char *ifname)
+{
+ ENTRY item;
+ ENTRY *ret_item;
+
+ item.key = ifname;
+ if (hsearch_r(item, FIND, &ret_item, &if_map) == 0)
+ return (NULL);
+
+ return (ret_item->data);
+}
+
+static struct pfctl_altq *
+qname_to_pfaltq(const char *qname, const char *ifname)
+{
+ ENTRY item;
+ ENTRY *ret_item;
+ char key[IFNAMSIZ + PF_QNAME_SIZE];
+
+ item.key = key;
+ snprintf(item.key, sizeof(key), "%s:%s", ifname, qname);
+ if (hsearch_r(item, FIND, &ret_item, &queue_map) == 0)
+ return (NULL);
+
+ return (ret_item->data);
+}
+
+static u_int32_t
+qname_to_qid(char *qname)
+{
+ ENTRY item;
+ ENTRY *ret_item;
+ uint32_t qid;
+
+ /*
+ * We guarantee that same named queues on different interfaces
+ * have the same qid.
+ */
+ item.key = qname;
+ if (hsearch_r(item, FIND, &ret_item, &qid_map) == 0)
+ return (0);
+
+ qid = *(uint32_t *)ret_item->data;
+ return (qid);
+}
+
+void
+print_altq(const struct pf_altq *a, unsigned int level,
+ struct node_queue_bw *bw, struct node_queue_opt *qopts)
+{
+ if (a->qname[0] != 0) {
+ print_queue(a, level, bw, 1, qopts);
+ return;
+ }
+
+#ifdef __FreeBSD__
+ if (a->local_flags & PFALTQ_FLAG_IF_REMOVED)
+ printf("INACTIVE ");
+#endif
+
+ printf("altq on %s ", a->ifname);
+
+ switch (a->scheduler) {
+ case ALTQT_CBQ:
+ if (!print_cbq_opts(a))
+ printf("cbq ");
+ break;
+ case ALTQT_PRIQ:
+ if (!print_priq_opts(a))
+ printf("priq ");
+ break;
+ case ALTQT_HFSC:
+ if (!print_hfsc_opts(a, qopts))
+ printf("hfsc ");
+ break;
+ case ALTQT_FAIRQ:
+ if (!print_fairq_opts(a, qopts))
+ printf("fairq ");
+ break;
+ case ALTQT_CODEL:
+ if (!print_codel_opts(a, qopts))
+ printf("codel ");
+ break;
+ }
+
+ if (bw != NULL && bw->bw_percent > 0) {
+ if (bw->bw_percent < 100)
+ printf("bandwidth %u%% ", bw->bw_percent);
+ } else
+ printf("bandwidth %s ", rate2str((double)a->ifbandwidth));
+
+ if (a->qlimit != DEFAULT_QLIMIT)
+ printf("qlimit %u ", a->qlimit);
+ printf("tbrsize %u ", a->tbrsize);
+}
+
+void
+print_queue(const struct pf_altq *a, unsigned int level,
+ struct node_queue_bw *bw, int print_interface,
+ struct node_queue_opt *qopts)
+{
+ unsigned int i;
+
+#ifdef __FreeBSD__
+ if (a->local_flags & PFALTQ_FLAG_IF_REMOVED)
+ printf("INACTIVE ");
+#endif
+ printf("queue ");
+ for (i = 0; i < level; ++i)
+ printf(" ");
+ printf("%s ", a->qname);
+ if (print_interface)
+ printf("on %s ", a->ifname);
+ if (a->scheduler == ALTQT_CBQ || a->scheduler == ALTQT_HFSC ||
+ a->scheduler == ALTQT_FAIRQ) {
+ if (bw != NULL && bw->bw_percent > 0) {
+ if (bw->bw_percent < 100)
+ printf("bandwidth %u%% ", bw->bw_percent);
+ } else
+ printf("bandwidth %s ", rate2str((double)a->bandwidth));
+ }
+ if (a->priority != DEFAULT_PRIORITY)
+ printf("priority %u ", a->priority);
+ if (a->qlimit != DEFAULT_QLIMIT)
+ printf("qlimit %u ", a->qlimit);
+ switch (a->scheduler) {
+ case ALTQT_CBQ:
+ print_cbq_opts(a);
+ break;
+ case ALTQT_PRIQ:
+ print_priq_opts(a);
+ break;
+ case ALTQT_HFSC:
+ print_hfsc_opts(a, qopts);
+ break;
+ case ALTQT_FAIRQ:
+ print_fairq_opts(a, qopts);
+ break;
+ }
+}
+
+/*
+ * eval_pfaltq computes the discipline parameters.
+ */
+int
+eval_pfaltq(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw,
+ struct node_queue_opt *opts)
+{
+ u_int64_t rate;
+ u_int size, errors = 0;
+
+ if (bw->bw_absolute > 0)
+ pa->ifbandwidth = bw->bw_absolute;
+ else
+ if ((rate = getifspeed(pa->ifname)) == 0) {
+ fprintf(stderr, "interface %s does not know its bandwidth, "
+ "please specify an absolute bandwidth\n",
+ pa->ifname);
+ errors++;
+ } else if ((pa->ifbandwidth = eval_bwspec(bw, rate)) == 0)
+ pa->ifbandwidth = rate;
+
+ /*
+ * Limit bandwidth to UINT_MAX for schedulers that aren't 64-bit ready.
+ */
+ if ((pa->scheduler != ALTQT_HFSC) && (pa->ifbandwidth > UINT_MAX)) {
+ pa->ifbandwidth = UINT_MAX;
+ warnx("interface %s bandwidth limited to %" PRIu64 " bps "
+ "because selected scheduler is 32-bit limited\n", pa->ifname,
+ pa->ifbandwidth);
+ }
+ errors += eval_queue_opts(pa, opts, pa->ifbandwidth);
+
+ /* if tbrsize is not specified, use heuristics */
+ if (pa->tbrsize == 0) {
+ rate = pa->ifbandwidth;
+ if (rate <= 1 * 1000 * 1000)
+ size = 1;
+ else if (rate <= 10 * 1000 * 1000)
+ size = 4;
+ else if (rate <= 200 * 1000 * 1000)
+ size = 8;
+ else if (rate <= 2500 * 1000 * 1000ULL)
+ size = 24;
+ else
+ size = 128;
+ size = size * getifmtu(pa->ifname);
+ pa->tbrsize = size;
+ }
+ return (errors);
+}
+
+/*
+ * check_commit_altq does consistency check for each interface
+ */
+int
+check_commit_altq(int dev, int opts)
+{
+ struct pfctl_altq *if_ppa;
+ int error = 0;
+
+ /* call the discipline check for each interface. */
+ STAILQ_FOREACH(if_ppa, &interfaces, meta.link) {
+ switch (if_ppa->pa.scheduler) {
+ case ALTQT_CBQ:
+ error = check_commit_cbq(dev, opts, if_ppa);
+ break;
+ case ALTQT_PRIQ:
+ error = check_commit_priq(dev, opts, if_ppa);
+ break;
+ case ALTQT_HFSC:
+ error = check_commit_hfsc(dev, opts, if_ppa);
+ break;
+ case ALTQT_FAIRQ:
+ error = check_commit_fairq(dev, opts, if_ppa);
+ break;
+ default:
+ break;
+ }
+ }
+ return (error);
+}
+
+/*
+ * eval_pfqueue computes the queue parameters.
+ */
+int
+eval_pfqueue(struct pfctl *pf, struct pf_altq *pa, struct node_queue_bw *bw,
+ struct node_queue_opt *opts)
+{
+ /* should be merged with expand_queue */
+ struct pfctl_altq *if_ppa, *parent;
+ int error = 0;
+
+ /* find the corresponding interface and copy fields used by queues */
+ if ((if_ppa = pfaltq_lookup(pa->ifname)) == NULL) {
+ fprintf(stderr, "altq not defined on %s\n", pa->ifname);
+ return (1);
+ }
+ pa->scheduler = if_ppa->pa.scheduler;
+ pa->ifbandwidth = if_ppa->pa.ifbandwidth;
+
+ if (qname_to_pfaltq(pa->qname, pa->ifname) != NULL) {
+ fprintf(stderr, "queue %s already exists on interface %s\n",
+ pa->qname, pa->ifname);
+ return (1);
+ }
+ pa->qid = qname_to_qid(pa->qname);
+
+ parent = NULL;
+ if (pa->parent[0] != 0) {
+ parent = qname_to_pfaltq(pa->parent, pa->ifname);
+ if (parent == NULL) {
+ fprintf(stderr, "parent %s not found for %s\n",
+ pa->parent, pa->qname);
+ return (1);
+ }
+ pa->parent_qid = parent->pa.qid;
+ }
+ if (pa->qlimit == 0)
+ pa->qlimit = DEFAULT_QLIMIT;
+
+ if (pa->scheduler == ALTQT_CBQ || pa->scheduler == ALTQT_HFSC ||
+ pa->scheduler == ALTQT_FAIRQ) {
+ pa->bandwidth = eval_bwspec(bw,
+ parent == NULL ? pa->ifbandwidth : parent->pa.bandwidth);
+
+ if (pa->bandwidth > pa->ifbandwidth) {
+ fprintf(stderr, "bandwidth for %s higher than "
+ "interface\n", pa->qname);
+ return (1);
+ }
+ /*
+ * If not HFSC, then check that the sum of the child
+ * bandwidths is less than the parent's bandwidth. For
+ * HFSC, the equivalent concept is to check that the sum of
+ * the child linkshare service curves are under the parent's
+ * linkshare service curve, and that check is performed by
+ * eval_pfqueue_hfsc().
+ */
+ if ((parent != NULL) && (pa->scheduler != ALTQT_HFSC)) {
+ if (pa->bandwidth > parent->pa.bandwidth) {
+ warnx("bandwidth for %s higher than parent",
+ pa->qname);
+ return (1);
+ }
+ parent->meta.bwsum += pa->bandwidth;
+ if (parent->meta.bwsum > parent->pa.bandwidth) {
+ warnx("the sum of the child bandwidth (%" PRIu64
+ ") higher than parent \"%s\" (%" PRIu64 ")",
+ parent->meta.bwsum, parent->pa.qname,
+ parent->pa.bandwidth);
+ }
+ }
+ }
+
+ if (eval_queue_opts(pa, opts,
+ parent == NULL ? pa->ifbandwidth : parent->pa.bandwidth))
+ return (1);
+
+ if (parent != NULL)
+ parent->meta.children++;
+
+ switch (pa->scheduler) {
+ case ALTQT_CBQ:
+ error = eval_pfqueue_cbq(pf, pa, if_ppa);
+ break;
+ case ALTQT_PRIQ:
+ error = eval_pfqueue_priq(pf, pa, if_ppa);
+ break;
+ case ALTQT_HFSC:
+ error = eval_pfqueue_hfsc(pf, pa, if_ppa, parent);
+ break;
+ case ALTQT_FAIRQ:
+ error = eval_pfqueue_fairq(pf, pa, if_ppa, parent);
+ break;
+ default:
+ break;
+ }
+ return (error);
+}
+
+/*
+ * CBQ support functions
+ */
+#define RM_FILTER_GAIN 5 /* log2 of gain, e.g., 5 => 31/32 */
+#define RM_NS_PER_SEC (1000000000)
+
+static int
+eval_pfqueue_cbq(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa)
+{
+ struct cbq_opts *opts;
+ u_int ifmtu;
+
+ if (pa->priority >= CBQ_MAXPRI) {
+ warnx("priority out of range: max %d", CBQ_MAXPRI - 1);
+ return (-1);
+ }
+
+ ifmtu = getifmtu(pa->ifname);
+ opts = &pa->pq_u.cbq_opts;
+
+ if (opts->pktsize == 0) { /* use default */
+ opts->pktsize = ifmtu;
+ if (opts->pktsize > MCLBYTES) /* do what TCP does */
+ opts->pktsize &= ~MCLBYTES;
+ } else if (opts->pktsize > ifmtu)
+ opts->pktsize = ifmtu;
+ if (opts->maxpktsize == 0) /* use default */
+ opts->maxpktsize = ifmtu;
+ else if (opts->maxpktsize > ifmtu)
+ opts->pktsize = ifmtu;
+
+ if (opts->pktsize > opts->maxpktsize)
+ opts->pktsize = opts->maxpktsize;
+
+ if (pa->parent[0] == 0)
+ opts->flags |= (CBQCLF_ROOTCLASS | CBQCLF_WRR);
+
+ if (pa->pq_u.cbq_opts.flags & CBQCLF_ROOTCLASS)
+ if_ppa->meta.root_classes++;
+ if (pa->pq_u.cbq_opts.flags & CBQCLF_DEFCLASS)
+ if_ppa->meta.default_classes++;
+
+ cbq_compute_idletime(pf, pa);
+ return (0);
+}
+
+/*
+ * compute ns_per_byte, maxidle, minidle, and offtime
+ */
+static int
+cbq_compute_idletime(struct pfctl *pf, struct pf_altq *pa)
+{
+ struct cbq_opts *opts;
+ double maxidle_s, maxidle, minidle;
+ double offtime, nsPerByte, ifnsPerByte, ptime, cptime;
+ double z, g, f, gton, gtom;
+ u_int minburst, maxburst;
+
+ opts = &pa->pq_u.cbq_opts;
+ ifnsPerByte = (1.0 / (double)pa->ifbandwidth) * RM_NS_PER_SEC * 8;
+ minburst = opts->minburst;
+ maxburst = opts->maxburst;
+
+ if (pa->bandwidth == 0)
+ f = 0.0001; /* small enough? */
+ else
+ f = ((double) pa->bandwidth / (double) pa->ifbandwidth);
+
+ nsPerByte = ifnsPerByte / f;
+ ptime = (double)opts->pktsize * ifnsPerByte;
+ cptime = ptime * (1.0 - f) / f;
+
+ if (nsPerByte * (double)opts->maxpktsize > (double)INT_MAX) {
+ /*
+ * this causes integer overflow in kernel!
+ * (bandwidth < 6Kbps when max_pkt_size=1500)
+ */
+ if (pa->bandwidth != 0 && (pf->opts & PF_OPT_QUIET) == 0) {
+ warnx("queue bandwidth must be larger than %s",
+ rate2str(ifnsPerByte * (double)opts->maxpktsize /
+ (double)INT_MAX * (double)pa->ifbandwidth));
+ fprintf(stderr, "cbq: queue %s is too slow!\n",
+ pa->qname);
+ }
+ nsPerByte = (double)(INT_MAX / opts->maxpktsize);
+ }
+
+ if (maxburst == 0) { /* use default */
+ if (cptime > 10.0 * 1000000)
+ maxburst = 4;
+ else
+ maxburst = 16;
+ }
+ if (minburst == 0) /* use default */
+ minburst = 2;
+ if (minburst > maxburst)
+ minburst = maxburst;
+
+ z = (double)(1 << RM_FILTER_GAIN);
+ g = (1.0 - 1.0 / z);
+ gton = pow(g, (double)maxburst);
+ gtom = pow(g, (double)(minburst-1));
+ maxidle = ((1.0 / f - 1.0) * ((1.0 - gton) / gton));
+ maxidle_s = (1.0 - g);
+ if (maxidle > maxidle_s)
+ maxidle = ptime * maxidle;
+ else
+ maxidle = ptime * maxidle_s;
+ offtime = cptime * (1.0 + 1.0/(1.0 - g) * (1.0 - gtom) / gtom);
+ minidle = -((double)opts->maxpktsize * (double)nsPerByte);
+
+ /* scale parameters */
+ maxidle = ((maxidle * 8.0) / nsPerByte) *
+ pow(2.0, (double)RM_FILTER_GAIN);
+ offtime = (offtime * 8.0) / nsPerByte *
+ pow(2.0, (double)RM_FILTER_GAIN);
+ minidle = ((minidle * 8.0) / nsPerByte) *
+ pow(2.0, (double)RM_FILTER_GAIN);
+
+ maxidle = maxidle / 1000.0;
+ offtime = offtime / 1000.0;
+ minidle = minidle / 1000.0;
+
+ opts->minburst = minburst;
+ opts->maxburst = maxburst;
+ opts->ns_per_byte = (u_int)nsPerByte;
+ opts->maxidle = (u_int)fabs(maxidle);
+ opts->minidle = (int)minidle;
+ opts->offtime = (u_int)fabs(offtime);
+
+ return (0);
+}
+
+static int
+check_commit_cbq(int dev, int opts, struct pfctl_altq *if_ppa)
+{
+ int error = 0;
+
+ /*
+ * check if cbq has one root queue and one default queue
+ * for this interface
+ */
+ if (if_ppa->meta.root_classes != 1) {
+ warnx("should have one root queue on %s", if_ppa->pa.ifname);
+ error++;
+ }
+ if (if_ppa->meta.default_classes != 1) {
+ warnx("should have one default queue on %s", if_ppa->pa.ifname);
+ error++;
+ }
+ return (error);
+}
+
+static int
+print_cbq_opts(const struct pf_altq *a)
+{
+ const struct cbq_opts *opts;
+
+ opts = &a->pq_u.cbq_opts;
+ if (opts->flags) {
+ printf("cbq(");
+ if (opts->flags & CBQCLF_RED)
+ printf(" red");
+ if (opts->flags & CBQCLF_ECN)
+ printf(" ecn");
+ if (opts->flags & CBQCLF_RIO)
+ printf(" rio");
+ if (opts->flags & CBQCLF_CODEL)
+ printf(" codel");
+ if (opts->flags & CBQCLF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & CBQCLF_FLOWVALVE)
+ printf(" flowvalve");
+ if (opts->flags & CBQCLF_BORROW)
+ printf(" borrow");
+ if (opts->flags & CBQCLF_WRR)
+ printf(" wrr");
+ if (opts->flags & CBQCLF_EFFICIENT)
+ printf(" efficient");
+ if (opts->flags & CBQCLF_ROOTCLASS)
+ printf(" root");
+ if (opts->flags & CBQCLF_DEFCLASS)
+ printf(" default");
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * PRIQ support functions
+ */
+static int
+eval_pfqueue_priq(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa)
+{
+
+ if (pa->priority >= PRIQ_MAXPRI) {
+ warnx("priority out of range: max %d", PRIQ_MAXPRI - 1);
+ return (-1);
+ }
+ if (BIT_ISSET(QPRI_BITSET_SIZE, pa->priority, &if_ppa->meta.qpris)) {
+ warnx("%s does not have a unique priority on interface %s",
+ pa->qname, pa->ifname);
+ return (-1);
+ } else
+ BIT_SET(QPRI_BITSET_SIZE, pa->priority, &if_ppa->meta.qpris);
+
+ if (pa->pq_u.priq_opts.flags & PRCF_DEFAULTCLASS)
+ if_ppa->meta.default_classes++;
+ return (0);
+}
+
+static int
+check_commit_priq(int dev, int opts, struct pfctl_altq *if_ppa)
+{
+
+ /*
+ * check if priq has one default class for this interface
+ */
+ if (if_ppa->meta.default_classes != 1) {
+ warnx("should have one default queue on %s", if_ppa->pa.ifname);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+print_priq_opts(const struct pf_altq *a)
+{
+ const struct priq_opts *opts;
+
+ opts = &a->pq_u.priq_opts;
+
+ if (opts->flags) {
+ printf("priq(");
+ if (opts->flags & PRCF_RED)
+ printf(" red");
+ if (opts->flags & PRCF_ECN)
+ printf(" ecn");
+ if (opts->flags & PRCF_RIO)
+ printf(" rio");
+ if (opts->flags & PRCF_CODEL)
+ printf(" codel");
+ if (opts->flags & PRCF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & PRCF_DEFAULTCLASS)
+ printf(" default");
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * HFSC support functions
+ */
+static int
+eval_pfqueue_hfsc(struct pfctl *pf, struct pf_altq *pa, struct pfctl_altq *if_ppa,
+ struct pfctl_altq *parent)
+{
+ struct hfsc_opts_v1 *opts;
+ struct service_curve sc;
+
+ opts = &pa->pq_u.hfsc_opts;
+
+ if (parent == NULL) {
+ /* root queue */
+ opts->lssc_m1 = pa->ifbandwidth;
+ opts->lssc_m2 = pa->ifbandwidth;
+ opts->lssc_d = 0;
+ return (0);
+ }
+
+ /* First child initializes the parent's service curve accumulators. */
+ if (parent->meta.children == 1) {
+ LIST_INIT(&parent->meta.rtsc);
+ LIST_INIT(&parent->meta.lssc);
+ }
+
+ if (parent->pa.pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS) {
+ warnx("adding %s would make default queue %s not a leaf",
+ pa->qname, pa->parent);
+ return (-1);
+ }
+
+ if (pa->pq_u.hfsc_opts.flags & HFCF_DEFAULTCLASS)
+ if_ppa->meta.default_classes++;
+
+ /* if link_share is not specified, use bandwidth */
+ if (opts->lssc_m2 == 0)
+ opts->lssc_m2 = pa->bandwidth;
+
+ if ((opts->rtsc_m1 > 0 && opts->rtsc_m2 == 0) ||
+ (opts->lssc_m1 > 0 && opts->lssc_m2 == 0) ||
+ (opts->ulsc_m1 > 0 && opts->ulsc_m2 == 0)) {
+ warnx("m2 is zero for %s", pa->qname);
+ return (-1);
+ }
+
+ if ((opts->rtsc_m1 < opts->rtsc_m2 && opts->rtsc_m1 != 0) ||
+ (opts->lssc_m1 < opts->lssc_m2 && opts->lssc_m1 != 0) ||
+ (opts->ulsc_m1 < opts->ulsc_m2 && opts->ulsc_m1 != 0)) {
+ warnx("m1 must be zero for convex curve: %s", pa->qname);
+ return (-1);
+ }
+
+ /*
+ * admission control:
+ * for the real-time service curve, the sum of the service curves
+ * should not exceed 80% of the interface bandwidth. 20% is reserved
+ * not to over-commit the actual interface bandwidth.
+ * for the linkshare service curve, the sum of the child service
+ * curve should not exceed the parent service curve.
+ * for the upper-limit service curve, the assigned bandwidth should
+ * be smaller than the interface bandwidth, and the upper-limit should
+ * be larger than the real-time service curve when both are defined.
+ */
+
+ /* check the real-time service curve. reserve 20% of interface bw */
+ if (opts->rtsc_m2 != 0) {
+ /* add this queue to the sum */
+ sc.m1 = opts->rtsc_m1;
+ sc.d = opts->rtsc_d;
+ sc.m2 = opts->rtsc_m2;
+ gsc_add_sc(&parent->meta.rtsc, &sc);
+ /* compare the sum with 80% of the interface */
+ sc.m1 = 0;
+ sc.d = 0;
+ sc.m2 = pa->ifbandwidth / 100 * 80;
+ if (!is_gsc_under_sc(&parent->meta.rtsc, &sc)) {
+ warnx("real-time sc exceeds 80%% of the interface "
+ "bandwidth (%s)", rate2str((double)sc.m2));
+ return (-1);
+ }
+ }
+
+ /* check the linkshare service curve. */
+ if (opts->lssc_m2 != 0) {
+ /* add this queue to the child sum */
+ sc.m1 = opts->lssc_m1;
+ sc.d = opts->lssc_d;
+ sc.m2 = opts->lssc_m2;
+ gsc_add_sc(&parent->meta.lssc, &sc);
+ /* compare the sum of the children with parent's sc */
+ sc.m1 = parent->pa.pq_u.hfsc_opts.lssc_m1;
+ sc.d = parent->pa.pq_u.hfsc_opts.lssc_d;
+ sc.m2 = parent->pa.pq_u.hfsc_opts.lssc_m2;
+ if (!is_gsc_under_sc(&parent->meta.lssc, &sc)) {
+ warnx("linkshare sc exceeds parent's sc");
+ return (-1);
+ }
+ }
+
+ /* check the upper-limit service curve. */
+ if (opts->ulsc_m2 != 0) {
+ if (opts->ulsc_m1 > pa->ifbandwidth ||
+ opts->ulsc_m2 > pa->ifbandwidth) {
+ warnx("upper-limit larger than interface bandwidth");
+ return (-1);
+ }
+ if (opts->rtsc_m2 != 0 && opts->rtsc_m2 > opts->ulsc_m2) {
+ warnx("upper-limit sc smaller than real-time sc");
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * FAIRQ support functions
+ */
+static int
+eval_pfqueue_fairq(struct pfctl *pf __unused, struct pf_altq *pa,
+ struct pfctl_altq *if_ppa, struct pfctl_altq *parent)
+{
+ struct fairq_opts *opts;
+ struct service_curve sc;
+
+ opts = &pa->pq_u.fairq_opts;
+
+ if (parent == NULL) {
+ /* root queue */
+ opts->lssc_m1 = pa->ifbandwidth;
+ opts->lssc_m2 = pa->ifbandwidth;
+ opts->lssc_d = 0;
+ return (0);
+ }
+
+ /* First child initializes the parent's service curve accumulator. */
+ if (parent->meta.children == 1)
+ LIST_INIT(&parent->meta.lssc);
+
+ if (parent->pa.pq_u.fairq_opts.flags & FARF_DEFAULTCLASS) {
+ warnx("adding %s would make default queue %s not a leaf",
+ pa->qname, pa->parent);
+ return (-1);
+ }
+
+ if (pa->pq_u.fairq_opts.flags & FARF_DEFAULTCLASS)
+ if_ppa->meta.default_classes++;
+
+ /* if link_share is not specified, use bandwidth */
+ if (opts->lssc_m2 == 0)
+ opts->lssc_m2 = pa->bandwidth;
+
+ /*
+ * admission control:
+ * for the real-time service curve, the sum of the service curves
+ * should not exceed 80% of the interface bandwidth. 20% is reserved
+ * not to over-commit the actual interface bandwidth.
+ * for the link-sharing service curve, the sum of the child service
+ * curve should not exceed the parent service curve.
+ * for the upper-limit service curve, the assigned bandwidth should
+ * be smaller than the interface bandwidth, and the upper-limit should
+ * be larger than the real-time service curve when both are defined.
+ */
+
+ /* check the linkshare service curve. */
+ if (opts->lssc_m2 != 0) {
+ /* add this queue to the child sum */
+ sc.m1 = opts->lssc_m1;
+ sc.d = opts->lssc_d;
+ sc.m2 = opts->lssc_m2;
+ gsc_add_sc(&parent->meta.lssc, &sc);
+ /* compare the sum of the children with parent's sc */
+ sc.m1 = parent->pa.pq_u.fairq_opts.lssc_m1;
+ sc.d = parent->pa.pq_u.fairq_opts.lssc_d;
+ sc.m2 = parent->pa.pq_u.fairq_opts.lssc_m2;
+ if (!is_gsc_under_sc(&parent->meta.lssc, &sc)) {
+ warnx("link-sharing sc exceeds parent's sc");
+ return (-1);
+ }
+ }
+
+ return (0);
+}
+
+static int
+check_commit_hfsc(int dev, int opts, struct pfctl_altq *if_ppa)
+{
+
+ /* check if hfsc has one default queue for this interface */
+ if (if_ppa->meta.default_classes != 1) {
+ warnx("should have one default queue on %s", if_ppa->pa.ifname);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+check_commit_fairq(int dev __unused, int opts __unused, struct pfctl_altq *if_ppa)
+{
+
+ /* check if fairq has one default queue for this interface */
+ if (if_ppa->meta.default_classes != 1) {
+ warnx("should have one default queue on %s", if_ppa->pa.ifname);
+ return (1);
+ }
+ return (0);
+}
+
+static int
+print_hfsc_opts(const struct pf_altq *a, const struct node_queue_opt *qopts)
+{
+ const struct hfsc_opts_v1 *opts;
+ const struct node_hfsc_sc *rtsc, *lssc, *ulsc;
+
+ opts = &a->pq_u.hfsc_opts;
+ if (qopts == NULL)
+ rtsc = lssc = ulsc = NULL;
+ else {
+ rtsc = &qopts->data.hfsc_opts.realtime;
+ lssc = &qopts->data.hfsc_opts.linkshare;
+ ulsc = &qopts->data.hfsc_opts.upperlimit;
+ }
+
+ if (opts->flags || opts->rtsc_m2 != 0 || opts->ulsc_m2 != 0 ||
+ (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))) {
+ printf("hfsc(");
+ if (opts->flags & HFCF_RED)
+ printf(" red");
+ if (opts->flags & HFCF_ECN)
+ printf(" ecn");
+ if (opts->flags & HFCF_RIO)
+ printf(" rio");
+ if (opts->flags & HFCF_CODEL)
+ printf(" codel");
+ if (opts->flags & HFCF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & HFCF_DEFAULTCLASS)
+ printf(" default");
+ if (opts->rtsc_m2 != 0)
+ print_hfsc_sc("realtime", opts->rtsc_m1, opts->rtsc_d,
+ opts->rtsc_m2, rtsc);
+ if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))
+ print_hfsc_sc("linkshare", opts->lssc_m1, opts->lssc_d,
+ opts->lssc_m2, lssc);
+ if (opts->ulsc_m2 != 0)
+ print_hfsc_sc("upperlimit", opts->ulsc_m1, opts->ulsc_d,
+ opts->ulsc_m2, ulsc);
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+static int
+print_codel_opts(const struct pf_altq *a, const struct node_queue_opt *qopts)
+{
+ const struct codel_opts *opts;
+
+ opts = &a->pq_u.codel_opts;
+ if (opts->target || opts->interval || opts->ecn) {
+ printf("codel(");
+ if (opts->target)
+ printf(" target %d", opts->target);
+ if (opts->interval)
+ printf(" interval %d", opts->interval);
+ if (opts->ecn)
+ printf("ecn");
+ printf(" ) ");
+
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+print_fairq_opts(const struct pf_altq *a, const struct node_queue_opt *qopts)
+{
+ const struct fairq_opts *opts;
+ const struct node_fairq_sc *loc_lssc;
+
+ opts = &a->pq_u.fairq_opts;
+ if (qopts == NULL)
+ loc_lssc = NULL;
+ else
+ loc_lssc = &qopts->data.fairq_opts.linkshare;
+
+ if (opts->flags ||
+ (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))) {
+ printf("fairq(");
+ if (opts->flags & FARF_RED)
+ printf(" red");
+ if (opts->flags & FARF_ECN)
+ printf(" ecn");
+ if (opts->flags & FARF_RIO)
+ printf(" rio");
+ if (opts->flags & FARF_CODEL)
+ printf(" codel");
+ if (opts->flags & FARF_CLEARDSCP)
+ printf(" cleardscp");
+ if (opts->flags & FARF_DEFAULTCLASS)
+ printf(" default");
+ if (opts->lssc_m2 != 0 && (opts->lssc_m2 != a->bandwidth ||
+ opts->lssc_d != 0))
+ print_fairq_sc("linkshare", opts->lssc_m1, opts->lssc_d,
+ opts->lssc_m2, loc_lssc);
+ printf(" ) ");
+
+ return (1);
+ } else
+ return (0);
+}
+
+/*
+ * admission control using generalized service curve
+ */
+
+/* add a new service curve to a generalized service curve */
+static void
+gsc_add_sc(struct gen_sc *gsc, struct service_curve *sc)
+{
+ if (is_sc_null(sc))
+ return;
+ if (sc->d != 0)
+ gsc_add_seg(gsc, 0.0, 0.0, (double)sc->d, (double)sc->m1);
+ gsc_add_seg(gsc, (double)sc->d, 0.0, INFINITY, (double)sc->m2);
+}
+
+/*
+ * check whether all points of a generalized service curve have
+ * their y-coordinates no larger than a given two-piece linear
+ * service curve.
+ */
+static int
+is_gsc_under_sc(struct gen_sc *gsc, struct service_curve *sc)
+{
+ struct segment *s, *last, *end;
+ double y;
+
+ if (is_sc_null(sc)) {
+ if (LIST_EMPTY(gsc))
+ return (1);
+ LIST_FOREACH(s, gsc, _next) {
+ if (s->m != 0)
+ return (0);
+ }
+ return (1);
+ }
+ /*
+ * gsc has a dummy entry at the end with x = INFINITY.
+ * loop through up to this dummy entry.
+ */
+ end = gsc_getentry(gsc, INFINITY);
+ if (end == NULL)
+ return (1);
+ last = NULL;
+ for (s = LIST_FIRST(gsc); s != end; s = LIST_NEXT(s, _next)) {
+ if (s->y > sc_x2y(sc, s->x))
+ return (0);
+ last = s;
+ }
+ /* last now holds the real last segment */
+ if (last == NULL)
+ return (1);
+ if (last->m > sc->m2)
+ return (0);
+ if (last->x < sc->d && last->m > sc->m1) {
+ y = last->y + (sc->d - last->x) * last->m;
+ if (y > sc_x2y(sc, sc->d))
+ return (0);
+ }
+ return (1);
+}
+
+/*
+ * return a segment entry starting at x.
+ * if gsc has no entry starting at x, a new entry is created at x.
+ */
+static struct segment *
+gsc_getentry(struct gen_sc *gsc, double x)
+{
+ struct segment *new, *prev, *s;
+
+ prev = NULL;
+ LIST_FOREACH(s, gsc, _next) {
+ if (s->x == x)
+ return (s); /* matching entry found */
+ else if (s->x < x)
+ prev = s;
+ else
+ break;
+ }
+
+ /* we have to create a new entry */
+ if ((new = calloc(1, sizeof(struct segment))) == NULL)
+ return (NULL);
+
+ new->x = x;
+ if (x == INFINITY || s == NULL)
+ new->d = 0;
+ else if (s->x == INFINITY)
+ new->d = INFINITY;
+ else
+ new->d = s->x - x;
+ if (prev == NULL) {
+ /* insert the new entry at the head of the list */
+ new->y = 0;
+ new->m = 0;
+ LIST_INSERT_HEAD(gsc, new, _next);
+ } else {
+ /*
+ * the start point intersects with the segment pointed by
+ * prev. divide prev into 2 segments
+ */
+ if (x == INFINITY) {
+ prev->d = INFINITY;
+ if (prev->m == 0)
+ new->y = prev->y;
+ else
+ new->y = INFINITY;
+ } else {
+ prev->d = x - prev->x;
+ new->y = prev->d * prev->m + prev->y;
+ }
+ new->m = prev->m;
+ LIST_INSERT_AFTER(prev, new, _next);
+ }
+ return (new);
+}
+
+/* add a segment to a generalized service curve */
+static int
+gsc_add_seg(struct gen_sc *gsc, double x, double y, double d, double m)
+{
+ struct segment *start, *end, *s;
+ double x2;
+
+ if (d == INFINITY)
+ x2 = INFINITY;
+ else
+ x2 = x + d;
+ start = gsc_getentry(gsc, x);
+ end = gsc_getentry(gsc, x2);
+ if (start == NULL || end == NULL)
+ return (-1);
+
+ for (s = start; s != end; s = LIST_NEXT(s, _next)) {
+ s->m += m;
+ s->y += y + (s->x - x) * m;
+ }
+
+ end = gsc_getentry(gsc, INFINITY);
+ for (; s != end; s = LIST_NEXT(s, _next)) {
+ s->y += m * d;
+ }
+
+ return (0);
+}
+
+/* get y-projection of a service curve */
+static double
+sc_x2y(struct service_curve *sc, double x)
+{
+ double y;
+
+ if (x <= (double)sc->d)
+ /* y belongs to the 1st segment */
+ y = x * (double)sc->m1;
+ else
+ /* y belongs to the 2nd segment */
+ y = (double)sc->d * (double)sc->m1
+ + (x - (double)sc->d) * (double)sc->m2;
+ return (y);
+}
+
+/*
+ * misc utilities
+ */
+#define R2S_BUFS 8
+#define RATESTR_MAX 16
+
+char *
+rate2str(double rate)
+{
+ char *buf;
+ static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring buffer */
+ static int idx = 0;
+ int i;
+ static const char unit[] = " KMG";
+
+ buf = r2sbuf[idx++];
+ if (idx == R2S_BUFS)
+ idx = 0;
+
+ for (i = 0; rate >= 1000 && i <= 3; i++)
+ rate /= 1000;
+
+ if ((int)(rate * 100) % 100)
+ snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
+ else
+ snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
+
+ return (buf);
+}
+
+u_int32_t
+getifspeed(char *ifname)
+{
+ int s;
+ struct ifreq ifr;
+ struct if_data ifrdat;
+
+ s = get_query_socket();
+ bzero(&ifr, sizeof(ifr));
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
+ sizeof(ifr.ifr_name))
+ errx(1, "getifspeed: strlcpy");
+ ifr.ifr_data = (caddr_t)&ifrdat;
+ if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1)
+ err(1, "SIOCGIFDATA");
+ return ((u_int32_t)ifrdat.ifi_baudrate);
+}
+
+u_long
+getifmtu(char *ifname)
+{
+ int s;
+ struct ifreq ifr;
+
+ s = get_query_socket();
+ bzero(&ifr, sizeof(ifr));
+ if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >=
+ sizeof(ifr.ifr_name))
+ errx(1, "getifmtu: strlcpy");
+ if (ioctl(s, SIOCGIFMTU, (caddr_t)&ifr) == -1)
+#ifdef __FreeBSD__
+ ifr.ifr_mtu = 1500;
+#else
+ err(1, "SIOCGIFMTU");
+#endif
+ if (ifr.ifr_mtu > 0)
+ return (ifr.ifr_mtu);
+ else {
+ warnx("could not get mtu for %s, assuming 1500", ifname);
+ return (1500);
+ }
+}
+
+int
+eval_queue_opts(struct pf_altq *pa, struct node_queue_opt *opts,
+ u_int64_t ref_bw)
+{
+ int errors = 0;
+
+ switch (pa->scheduler) {
+ case ALTQT_CBQ:
+ pa->pq_u.cbq_opts = opts->data.cbq_opts;
+ break;
+ case ALTQT_PRIQ:
+ pa->pq_u.priq_opts = opts->data.priq_opts;
+ break;
+ case ALTQT_HFSC:
+ pa->pq_u.hfsc_opts.flags = opts->data.hfsc_opts.flags;
+ if (opts->data.hfsc_opts.linkshare.used) {
+ pa->pq_u.hfsc_opts.lssc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.linkshare.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.lssc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.linkshare.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.lssc_d =
+ opts->data.hfsc_opts.linkshare.d;
+ }
+ if (opts->data.hfsc_opts.realtime.used) {
+ pa->pq_u.hfsc_opts.rtsc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.realtime.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.rtsc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.realtime.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.rtsc_d =
+ opts->data.hfsc_opts.realtime.d;
+ }
+ if (opts->data.hfsc_opts.upperlimit.used) {
+ pa->pq_u.hfsc_opts.ulsc_m1 =
+ eval_bwspec(&opts->data.hfsc_opts.upperlimit.m1,
+ ref_bw);
+ pa->pq_u.hfsc_opts.ulsc_m2 =
+ eval_bwspec(&opts->data.hfsc_opts.upperlimit.m2,
+ ref_bw);
+ pa->pq_u.hfsc_opts.ulsc_d =
+ opts->data.hfsc_opts.upperlimit.d;
+ }
+ break;
+ case ALTQT_FAIRQ:
+ pa->pq_u.fairq_opts.flags = opts->data.fairq_opts.flags;
+ pa->pq_u.fairq_opts.nbuckets = opts->data.fairq_opts.nbuckets;
+ pa->pq_u.fairq_opts.hogs_m1 =
+ eval_bwspec(&opts->data.fairq_opts.hogs_bw, ref_bw);
+
+ if (opts->data.fairq_opts.linkshare.used) {
+ pa->pq_u.fairq_opts.lssc_m1 =
+ eval_bwspec(&opts->data.fairq_opts.linkshare.m1,
+ ref_bw);
+ pa->pq_u.fairq_opts.lssc_m2 =
+ eval_bwspec(&opts->data.fairq_opts.linkshare.m2,
+ ref_bw);
+ pa->pq_u.fairq_opts.lssc_d =
+ opts->data.fairq_opts.linkshare.d;
+ }
+ break;
+ case ALTQT_CODEL:
+ pa->pq_u.codel_opts.target = opts->data.codel_opts.target;
+ pa->pq_u.codel_opts.interval = opts->data.codel_opts.interval;
+ pa->pq_u.codel_opts.ecn = opts->data.codel_opts.ecn;
+ break;
+ default:
+ warnx("eval_queue_opts: unknown scheduler type %u",
+ opts->qtype);
+ errors++;
+ break;
+ }
+
+ return (errors);
+}
+
+/*
+ * If absolute bandwidth if set, return the lesser of that value and the
+ * reference bandwidth. Limiting to the reference bandwidth allows simple
+ * limiting of configured bandwidth parameters for schedulers that are
+ * 32-bit limited, as the root/interface bandwidth (top-level reference
+ * bandwidth) will be properly limited in that case.
+ *
+ * Otherwise, if the absolute bandwidth is not set, return given percentage
+ * of reference bandwidth.
+ */
+u_int64_t
+eval_bwspec(struct node_queue_bw *bw, u_int64_t ref_bw)
+{
+ if (bw->bw_absolute > 0)
+ return (MIN(bw->bw_absolute, ref_bw));
+
+ if (bw->bw_percent > 0)
+ return (ref_bw / 100 * bw->bw_percent);
+
+ return (0);
+}
+
+void
+print_hfsc_sc(const char *scname, u_int m1, u_int d, u_int m2,
+ const struct node_hfsc_sc *sc)
+{
+ printf(" %s", scname);
+
+ if (d != 0) {
+ printf("(");
+ if (sc != NULL && sc->m1.bw_percent > 0)
+ printf("%u%%", sc->m1.bw_percent);
+ else
+ printf("%s", rate2str((double)m1));
+ printf(" %u", d);
+ }
+
+ if (sc != NULL && sc->m2.bw_percent > 0)
+ printf(" %u%%", sc->m2.bw_percent);
+ else
+ printf(" %s", rate2str((double)m2));
+
+ if (d != 0)
+ printf(")");
+}
+
+void
+print_fairq_sc(const char *scname, u_int m1, u_int d, u_int m2,
+ const struct node_fairq_sc *sc)
+{
+ printf(" %s", scname);
+
+ if (d != 0) {
+ printf("(");
+ if (sc != NULL && sc->m1.bw_percent > 0)
+ printf("%u%%", sc->m1.bw_percent);
+ else
+ printf("%s", rate2str((double)m1));
+ printf(" %u", d);
+ }
+
+ if (sc != NULL && sc->m2.bw_percent > 0)
+ printf(" %u%%", sc->m2.bw_percent);
+ else
+ printf(" %s", rate2str((double)m2));
+
+ if (d != 0)
+ printf(")");
+}
diff --git a/sbin/pfctl/pfctl_ioctl.h b/sbin/pfctl/pfctl_ioctl.h
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/sbin/pfctl/pfctl_ioctl.h
diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c
new file mode 100644
index 000000000000..2d16bbd22b39
--- /dev/null
+++ b/sbin/pfctl/pfctl_optimize.c
@@ -0,0 +1,1684 @@
+/* $OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */
+
+/*
+ * Copyright (c) 2004 Mike Frantzen <frantzen@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <libpfctl.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+/* The size at which a table becomes faster than individual rules */
+#define TABLE_THRESHOLD 6
+
+
+/* #define OPT_DEBUG 1 */
+#ifdef OPT_DEBUG
+# define DEBUG(str, v...) \
+ printf("%s: " str "\n", __FUNCTION__ , ## v)
+#else
+# define DEBUG(str, v...) ((void)0)
+#endif
+
+
+/*
+ * A container that lets us sort a superblock to optimize the skip step jumps
+ */
+struct pf_skip_step {
+ int ps_count; /* number of items */
+ TAILQ_HEAD( , pf_opt_rule) ps_rules;
+ TAILQ_ENTRY(pf_skip_step) ps_entry;
+};
+
+
+/*
+ * A superblock is a block of adjacent rules of similar action. If there
+ * are five PASS rules in a row, they all become members of a superblock.
+ * Once we have a superblock, we are free to re-order any rules within it
+ * in order to improve performance; if a packet is passed, it doesn't matter
+ * who passed it.
+ */
+struct superblock {
+ TAILQ_HEAD( , pf_opt_rule) sb_rules;
+ TAILQ_ENTRY(superblock) sb_entry;
+ struct superblock *sb_profiled_block;
+ TAILQ_HEAD(skiplist, pf_skip_step) sb_skipsteps[PF_SKIP_COUNT];
+};
+TAILQ_HEAD(superblocks, superblock);
+
+
+/*
+ * Description of the PF rule structure.
+ */
+enum {
+ BARRIER, /* the presence of the field puts the rule in its own block */
+ BREAK, /* the field may not differ between rules in a superblock */
+ NOMERGE, /* the field may not differ between rules when combined */
+ COMBINED, /* the field may itself be combined with other rules */
+ DC, /* we just don't care about the field */
+ NEVER}; /* we should never see this field set?!? */
+static struct pf_rule_field {
+ const char *prf_name;
+ int prf_type;
+ size_t prf_offset;
+ size_t prf_size;
+} pf_rule_desc[] = {
+#define PF_RULE_FIELD(field, ty) \
+ {#field, \
+ ty, \
+ offsetof(struct pfctl_rule, field), \
+ sizeof(((struct pfctl_rule *)0)->field)}
+
+
+ /*
+ * The presence of these fields in a rule put the rule in its own
+ * superblock. Thus it will not be optimized. It also prevents the
+ * rule from being re-ordered at all.
+ */
+ PF_RULE_FIELD(label, BARRIER),
+ PF_RULE_FIELD(prob, BARRIER),
+ PF_RULE_FIELD(max_states, BARRIER),
+ PF_RULE_FIELD(max_src_nodes, BARRIER),
+ PF_RULE_FIELD(max_src_states, BARRIER),
+ PF_RULE_FIELD(max_src_conn, BARRIER),
+ PF_RULE_FIELD(max_src_conn_rate, BARRIER),
+ PF_RULE_FIELD(anchor, BARRIER), /* for now */
+
+ /*
+ * These fields must be the same between all rules in the same superblock.
+ * These rules are allowed to be re-ordered but only among like rules.
+ * For instance we can re-order all 'tag "foo"' rules because they have the
+ * same tag. But we can not re-order between a 'tag "foo"' and a
+ * 'tag "bar"' since that would change the meaning of the ruleset.
+ */
+ PF_RULE_FIELD(tagname, BREAK),
+ PF_RULE_FIELD(keep_state, BREAK),
+ PF_RULE_FIELD(qname, BREAK),
+ PF_RULE_FIELD(pqname, BREAK),
+ PF_RULE_FIELD(rt, BREAK),
+ PF_RULE_FIELD(allow_opts, BREAK),
+ PF_RULE_FIELD(rule_flag, BREAK),
+ PF_RULE_FIELD(action, BREAK),
+ PF_RULE_FIELD(log, BREAK),
+ PF_RULE_FIELD(quick, BREAK),
+ PF_RULE_FIELD(return_ttl, BREAK),
+ PF_RULE_FIELD(overload_tblname, BREAK),
+ PF_RULE_FIELD(flush, BREAK),
+ PF_RULE_FIELD(rdr, BREAK),
+ PF_RULE_FIELD(nat, BREAK),
+ PF_RULE_FIELD(route, BREAK),
+ PF_RULE_FIELD(logif, BREAK),
+
+ /*
+ * Any fields not listed in this structure act as BREAK fields
+ */
+
+
+ /*
+ * These fields must not differ when we merge two rules together but
+ * their difference isn't enough to put the rules in different superblocks.
+ * There are no problems re-ordering any rules with these fields.
+ */
+ PF_RULE_FIELD(af, NOMERGE),
+ PF_RULE_FIELD(ifnot, NOMERGE),
+ PF_RULE_FIELD(ifname, NOMERGE), /* hack for IF groups */
+ PF_RULE_FIELD(match_tag_not, NOMERGE),
+ PF_RULE_FIELD(match_tagname, NOMERGE),
+ PF_RULE_FIELD(os_fingerprint, NOMERGE),
+ PF_RULE_FIELD(timeout, NOMERGE),
+ PF_RULE_FIELD(return_icmp, NOMERGE),
+ PF_RULE_FIELD(return_icmp6, NOMERGE),
+ PF_RULE_FIELD(uid, NOMERGE),
+ PF_RULE_FIELD(gid, NOMERGE),
+ PF_RULE_FIELD(direction, NOMERGE),
+ PF_RULE_FIELD(proto, NOMERGE),
+ PF_RULE_FIELD(type, NOMERGE),
+ PF_RULE_FIELD(code, NOMERGE),
+ PF_RULE_FIELD(flags, NOMERGE),
+ PF_RULE_FIELD(flagset, NOMERGE),
+ PF_RULE_FIELD(tos, NOMERGE),
+ PF_RULE_FIELD(src.port, NOMERGE),
+ PF_RULE_FIELD(dst.port, NOMERGE),
+ PF_RULE_FIELD(src.port_op, NOMERGE),
+ PF_RULE_FIELD(dst.port_op, NOMERGE),
+ PF_RULE_FIELD(src.neg, NOMERGE),
+ PF_RULE_FIELD(dst.neg, NOMERGE),
+ PF_RULE_FIELD(af, NOMERGE),
+
+ /* These fields can be merged */
+ PF_RULE_FIELD(src.addr, COMBINED),
+ PF_RULE_FIELD(dst.addr, COMBINED),
+
+ /* We just don't care about these fields. They're set by the kernel */
+ PF_RULE_FIELD(skip, DC),
+ PF_RULE_FIELD(evaluations, DC),
+ PF_RULE_FIELD(packets, DC),
+ PF_RULE_FIELD(bytes, DC),
+ PF_RULE_FIELD(kif, DC),
+ PF_RULE_FIELD(states_cur, DC),
+ PF_RULE_FIELD(states_tot, DC),
+ PF_RULE_FIELD(src_nodes, DC),
+ PF_RULE_FIELD(nr, DC),
+ PF_RULE_FIELD(entries, DC),
+ PF_RULE_FIELD(qid, DC),
+ PF_RULE_FIELD(pqid, DC),
+ PF_RULE_FIELD(anchor_relative, DC),
+ PF_RULE_FIELD(anchor_wildcard, DC),
+ PF_RULE_FIELD(tag, DC),
+ PF_RULE_FIELD(match_tag, DC),
+ PF_RULE_FIELD(overload_tbl, DC),
+
+ /* These fields should never be set in a PASS/BLOCK rule */
+ PF_RULE_FIELD(natpass, NEVER),
+ PF_RULE_FIELD(max_mss, NEVER),
+ PF_RULE_FIELD(min_ttl, NEVER),
+ PF_RULE_FIELD(set_tos, NEVER),
+};
+
+
+
+int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t,
+ struct pf_rule_addr *);
+int addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *);
+int addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *);
+int block_feedback(struct pfctl *, struct superblock *);
+int combine_rules(struct pfctl *, struct superblock *);
+void comparable_rule(struct pfctl_rule *, const struct pfctl_rule *, int);
+int construct_superblocks(struct pfctl *, struct pf_opt_queue *,
+ struct superblocks *);
+void exclude_supersets(struct pfctl_rule *, struct pfctl_rule *);
+int interface_group(const char *);
+int load_feedback_profile(struct pfctl *, struct superblocks *);
+int optimize_superblock(struct pfctl *, struct superblock *);
+int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *);
+void remove_from_skipsteps(struct skiplist *, struct superblock *,
+ struct pf_opt_rule *, struct pf_skip_step *);
+int remove_identical_rules(struct pfctl *, struct superblock *);
+int reorder_rules(struct pfctl *, struct superblock *, int);
+int rules_combineable(struct pfctl_rule *, struct pfctl_rule *);
+void skip_append(struct superblock *, int, struct pf_skip_step *,
+ struct pf_opt_rule *);
+int skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *);
+void skip_init(void);
+int skip_cmp_af(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_dir(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_dst_addr(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_dst_port(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_ifp(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_proto(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_src_addr(struct pfctl_rule *, struct pfctl_rule *);
+int skip_cmp_src_port(struct pfctl_rule *, struct pfctl_rule *);
+int superblock_inclusive(struct superblock *, struct pf_opt_rule *);
+void superblock_free(struct pfctl *, struct superblock *);
+struct pf_opt_tbl *pf_opt_table_ref(struct pf_opt_tbl *);
+void pf_opt_table_unref(struct pf_opt_tbl *);
+
+
+static int (*skip_comparitors[PF_SKIP_COUNT])(struct pfctl_rule *,
+ struct pfctl_rule *);
+static const char *skip_comparitors_names[PF_SKIP_COUNT];
+#define PF_SKIP_COMPARITORS { \
+ { "ifp", PF_SKIP_IFP, skip_cmp_ifp }, \
+ { "dir", PF_SKIP_DIR, skip_cmp_dir }, \
+ { "af", PF_SKIP_AF, skip_cmp_af }, \
+ { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \
+ { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \
+ { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \
+ { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \
+ { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \
+}
+
+static struct pfr_buffer table_buffer;
+static int table_identifier;
+
+
+int
+pfctl_optimize_ruleset(struct pfctl *pf, struct pfctl_ruleset *rs)
+{
+ struct superblocks superblocks;
+ struct pf_opt_queue opt_queue;
+ struct superblock *block;
+ struct pf_opt_rule *por;
+ struct pfctl_rule *r;
+ struct pfctl_rulequeue *old_rules;
+
+ if (TAILQ_EMPTY(rs->rules[PF_RULESET_FILTER].active.ptr))
+ return (0);
+
+ DEBUG("optimizing ruleset \"%s\"", rs->anchor->path);
+ memset(&table_buffer, 0, sizeof(table_buffer));
+ skip_init();
+ TAILQ_INIT(&opt_queue);
+
+ old_rules = rs->rules[PF_RULESET_FILTER].active.ptr;
+ rs->rules[PF_RULESET_FILTER].active.ptr =
+ rs->rules[PF_RULESET_FILTER].inactive.ptr;
+ rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules;
+
+ /*
+ * XXX expanding the pf_opt_rule format throughout pfctl might allow
+ * us to avoid all this copying.
+ */
+ while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr))
+ != NULL) {
+ TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r,
+ entries);
+ if ((por = calloc(1, sizeof(*por))) == NULL)
+ err(1, "calloc");
+ memcpy(&por->por_rule, r, sizeof(*r));
+ if (TAILQ_FIRST(&r->rdr.list) != NULL) {
+ TAILQ_INIT(&por->por_rule.rdr.list);
+ pfctl_move_pool(&r->rdr, &por->por_rule.rdr);
+ } else
+ bzero(&por->por_rule.rdr,
+ sizeof(por->por_rule.rdr));
+ if (TAILQ_FIRST(&r->nat.list) != NULL) {
+ TAILQ_INIT(&por->por_rule.nat.list);
+ pfctl_move_pool(&r->nat, &por->por_rule.nat);
+ } else
+ bzero(&por->por_rule.nat,
+ sizeof(por->por_rule.nat));
+ if (TAILQ_FIRST(&r->route.list) != NULL) {
+ TAILQ_INIT(&por->por_rule.route.list);
+ pfctl_move_pool(&r->route, &por->por_rule.route);
+ } else
+ bzero(&por->por_rule.route,
+ sizeof(por->por_rule.route));
+
+ TAILQ_INSERT_TAIL(&opt_queue, por, por_entry);
+ }
+
+ TAILQ_INIT(&superblocks);
+ if (construct_superblocks(pf, &opt_queue, &superblocks))
+ goto error;
+
+ if (pf->optimize & PF_OPTIMIZE_PROFILE) {
+ if (load_feedback_profile(pf, &superblocks))
+ goto error;
+ }
+
+ TAILQ_FOREACH(block, &superblocks, sb_entry) {
+ if (optimize_superblock(pf, block))
+ goto error;
+ }
+
+ rs->anchor->refcnt = 0;
+ while ((block = TAILQ_FIRST(&superblocks))) {
+ TAILQ_REMOVE(&superblocks, block, sb_entry);
+
+ while ((por = TAILQ_FIRST(&block->sb_rules))) {
+ TAILQ_REMOVE(&block->sb_rules, por, por_entry);
+ por->por_rule.nr = rs->anchor->refcnt++;
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ err(1, "calloc");
+ memcpy(r, &por->por_rule, sizeof(*r));
+ TAILQ_INIT(&r->rdr.list);
+ pfctl_move_pool(&por->por_rule.rdr, &r->rdr);
+ TAILQ_INIT(&r->nat.list);
+ pfctl_move_pool(&por->por_rule.nat, &r->nat);
+ TAILQ_INSERT_TAIL(
+ rs->rules[PF_RULESET_FILTER].active.ptr,
+ r, entries);
+ pf_opt_table_unref(por->por_src_tbl);
+ pf_opt_table_unref(por->por_dst_tbl);
+ free(por);
+ }
+ superblock_free(pf, block);
+ }
+
+ return (0);
+
+error:
+ while ((por = TAILQ_FIRST(&opt_queue))) {
+ TAILQ_REMOVE(&opt_queue, por, por_entry);
+ pf_opt_table_unref(por->por_src_tbl);
+ pf_opt_table_unref(por->por_dst_tbl);
+ free(por);
+ }
+ while ((block = TAILQ_FIRST(&superblocks))) {
+ TAILQ_REMOVE(&superblocks, block, sb_entry);
+ superblock_free(pf, block);
+ }
+ return (1);
+}
+
+
+/*
+ * Go ahead and optimize a superblock
+ */
+int
+optimize_superblock(struct pfctl *pf, struct superblock *block)
+{
+#ifdef OPT_DEBUG
+ struct pf_opt_rule *por;
+#endif /* OPT_DEBUG */
+
+ /* We have a few optimization passes:
+ * 1) remove duplicate rules or rules that are a subset of other
+ * rules
+ * 2) combine otherwise identical rules with different IP addresses
+ * into a single rule and put the addresses in a table.
+ * 3) re-order the rules to improve kernel skip steps
+ * 4) re-order the 'quick' rules based on feedback from the
+ * active ruleset statistics
+ *
+ * XXX combine_rules() doesn't combine v4 and v6 rules. would just
+ * have to keep af in the table container, make af 'COMBINE' and
+ * twiddle the af on the merged rule
+ * XXX maybe add a weighting to the metric on skipsteps when doing
+ * reordering. sometimes two sequential tables will be better
+ * that four consecutive interfaces.
+ * XXX need to adjust the skipstep count of everything after PROTO,
+ * since they aren't actually checked on a proto mismatch in
+ * pf_test_{tcp, udp, icmp}()
+ * XXX should i treat proto=0, af=0 or dir=0 special in skepstep
+ * calculation since they are a DC?
+ * XXX keep last skiplist of last superblock to influence this
+ * superblock. '5 inet6 log' should make '3 inet6' come before '4
+ * inet' in the next superblock.
+ * XXX would be useful to add tables for ports
+ * XXX we can also re-order some mutually exclusive superblocks to
+ * try merging superblocks before any of these optimization passes.
+ * for instance a single 'log in' rule in the middle of non-logging
+ * out rules.
+ */
+
+ /* shortcut. there will be a lot of 1-rule superblocks */
+ if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry))
+ return (0);
+
+#ifdef OPT_DEBUG
+ printf("--- Superblock ---\n");
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
+ printf(" ");
+ print_rule(&por->por_rule, por->por_rule.anchor ?
+ por->por_rule.anchor->name : "", 1, 0);
+ }
+#endif /* OPT_DEBUG */
+
+
+ if (remove_identical_rules(pf, block))
+ return (1);
+ if (combine_rules(pf, block))
+ return (1);
+ if ((pf->optimize & PF_OPTIMIZE_PROFILE) &&
+ TAILQ_FIRST(&block->sb_rules)->por_rule.quick &&
+ block->sb_profiled_block) {
+ if (block_feedback(pf, block))
+ return (1);
+ } else if (reorder_rules(pf, block, 0)) {
+ return (1);
+ }
+
+ /*
+ * Don't add any optimization passes below reorder_rules(). It will
+ * have divided superblocks into smaller blocks for further refinement
+ * and doesn't put them back together again. What once was a true
+ * superblock might have been split into multiple superblocks.
+ */
+
+#ifdef OPT_DEBUG
+ printf("--- END Superblock ---\n");
+#endif /* OPT_DEBUG */
+ return (0);
+}
+
+
+/*
+ * Optimization pass #1: remove identical rules
+ */
+int
+remove_identical_rules(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *por1, *por2, *por_next, *por2_next;
+ struct pfctl_rule a, a2, b, b2;
+
+ for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) {
+ por_next = TAILQ_NEXT(por1, por_entry);
+ for (por2 = por_next; por2; por2 = por2_next) {
+ por2_next = TAILQ_NEXT(por2, por_entry);
+ comparable_rule(&a, &por1->por_rule, DC);
+ comparable_rule(&b, &por2->por_rule, DC);
+ memcpy(&a2, &a, sizeof(a2));
+ memcpy(&b2, &b, sizeof(b2));
+
+ exclude_supersets(&a, &b);
+ exclude_supersets(&b2, &a2);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ DEBUG("removing identical rule nr%d = *nr%d*",
+ por1->por_rule.nr, por2->por_rule.nr);
+ TAILQ_REMOVE(&block->sb_rules, por2, por_entry);
+ if (por_next == por2)
+ por_next = TAILQ_NEXT(por1, por_entry);
+ free(por2);
+ } else if (memcmp(&a2, &b2, sizeof(a2)) == 0) {
+ DEBUG("removing identical rule *nr%d* = nr%d",
+ por1->por_rule.nr, por2->por_rule.nr);
+ TAILQ_REMOVE(&block->sb_rules, por1, por_entry);
+ free(por1);
+ break;
+ }
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #2: combine similar rules with different addresses
+ * into a single rule and a table
+ */
+int
+combine_rules(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *p1, *p2, *por_next;
+ int src_eq, dst_eq;
+
+ if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) {
+ warnx("Must enable table loading for optimizations");
+ return (1);
+ }
+
+ /* First we make a pass to combine the rules. O(n log n) */
+ TAILQ_FOREACH(p1, &block->sb_rules, por_entry) {
+ for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) {
+ por_next = TAILQ_NEXT(p2, por_entry);
+
+ src_eq = addrs_equal(&p1->por_rule.src,
+ &p2->por_rule.src);
+ dst_eq = addrs_equal(&p1->por_rule.dst,
+ &p2->por_rule.dst);
+
+ if (src_eq && !dst_eq && p1->por_src_tbl == NULL &&
+ p2->por_dst_tbl == NULL &&
+ p2->por_src_tbl == NULL &&
+ rules_combineable(&p1->por_rule, &p2->por_rule) &&
+ addrs_combineable(&p1->por_rule.dst,
+ &p2->por_rule.dst)) {
+ DEBUG("can combine rules nr%d = nr%d",
+ p1->por_rule.nr, p2->por_rule.nr);
+ if (p1->por_dst_tbl == NULL &&
+ add_opt_table(pf, &p1->por_dst_tbl,
+ p1->por_rule.af, &p1->por_rule.dst))
+ return (1);
+ if (add_opt_table(pf, &p1->por_dst_tbl,
+ p1->por_rule.af, &p2->por_rule.dst))
+ return (1);
+ if (p1->por_dst_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ TAILQ_REMOVE(&block->sb_rules, p2,
+ por_entry);
+ free(p2);
+ } else {
+ p2->por_dst_tbl =
+ pf_opt_table_ref(p1->por_dst_tbl);
+ }
+ } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL
+ && p2->por_src_tbl == NULL &&
+ p2->por_dst_tbl == NULL &&
+ rules_combineable(&p1->por_rule, &p2->por_rule) &&
+ addrs_combineable(&p1->por_rule.src,
+ &p2->por_rule.src)) {
+ DEBUG("can combine rules nr%d = nr%d",
+ p1->por_rule.nr, p2->por_rule.nr);
+ if (p1->por_src_tbl == NULL &&
+ add_opt_table(pf, &p1->por_src_tbl,
+ p1->por_rule.af, &p1->por_rule.src))
+ return (1);
+ if (add_opt_table(pf, &p1->por_src_tbl,
+ p1->por_rule.af, &p2->por_rule.src))
+ return (1);
+ if (p1->por_src_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ TAILQ_REMOVE(&block->sb_rules, p2,
+ por_entry);
+ free(p2);
+ } else {
+ p2->por_src_tbl =
+ pf_opt_table_ref(p1->por_src_tbl);
+ }
+ }
+ }
+ }
+
+
+ /*
+ * Then we make a final pass to create a valid table name and
+ * insert the name into the rules.
+ */
+ for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) {
+ por_next = TAILQ_NEXT(p1, por_entry);
+ assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL);
+
+ if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ if (p1->por_src_tbl->pt_generated) {
+ /* This rule is included in a table */
+ TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
+ free(p1);
+ continue;
+ }
+ p1->por_src_tbl->pt_generated = 1;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ pf_opt_create_table(pf, p1->por_src_tbl))
+ return (1);
+
+ pf->tdirty = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(p1->por_src_tbl->pt_name,
+ PFR_TFLAG_CONST, 1,
+ &p1->por_src_tbl->pt_nodes);
+
+ memset(&p1->por_rule.src.addr, 0,
+ sizeof(p1->por_rule.src.addr));
+ p1->por_rule.src.addr.type = PF_ADDR_TABLE;
+ strlcpy(p1->por_rule.src.addr.v.tblname,
+ p1->por_src_tbl->pt_name,
+ sizeof(p1->por_rule.src.addr.v.tblname));
+
+ pfr_buf_clear(p1->por_src_tbl->pt_buf);
+ free(p1->por_src_tbl->pt_buf);
+ p1->por_src_tbl->pt_buf = NULL;
+ }
+ if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >=
+ TABLE_THRESHOLD) {
+ if (p1->por_dst_tbl->pt_generated) {
+ /* This rule is included in a table */
+ TAILQ_REMOVE(&block->sb_rules, p1, por_entry);
+ free(p1);
+ continue;
+ }
+ p1->por_dst_tbl->pt_generated = 1;
+
+ if ((pf->opts & PF_OPT_NOACTION) == 0 &&
+ pf_opt_create_table(pf, p1->por_dst_tbl))
+ return (1);
+ pf->tdirty = 1;
+
+ if (pf->opts & PF_OPT_VERBOSE)
+ print_tabledef(p1->por_dst_tbl->pt_name,
+ PFR_TFLAG_CONST, 1,
+ &p1->por_dst_tbl->pt_nodes);
+
+ memset(&p1->por_rule.dst.addr, 0,
+ sizeof(p1->por_rule.dst.addr));
+ p1->por_rule.dst.addr.type = PF_ADDR_TABLE;
+ strlcpy(p1->por_rule.dst.addr.v.tblname,
+ p1->por_dst_tbl->pt_name,
+ sizeof(p1->por_rule.dst.addr.v.tblname));
+
+ pfr_buf_clear(p1->por_dst_tbl->pt_buf);
+ free(p1->por_dst_tbl->pt_buf);
+ p1->por_dst_tbl->pt_buf = NULL;
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #3: re-order rules to improve skip steps
+ */
+int
+reorder_rules(struct pfctl *pf, struct superblock *block, int depth)
+{
+ struct superblock *newblock;
+ struct pf_skip_step *skiplist;
+ struct pf_opt_rule *por;
+ int i, largest, largest_list, rule_count = 0;
+ TAILQ_HEAD( , pf_opt_rule) head;
+
+ /*
+ * Calculate the best-case skip steps. We put each rule in a list
+ * of other rules with common fields
+ */
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry) {
+ TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i],
+ ps_entry) {
+ if (skip_compare(i, skiplist, por) == 0)
+ break;
+ }
+ if (skiplist == NULL) {
+ if ((skiplist = calloc(1, sizeof(*skiplist))) ==
+ NULL)
+ err(1, "calloc");
+ TAILQ_INIT(&skiplist->ps_rules);
+ TAILQ_INSERT_TAIL(&block->sb_skipsteps[i],
+ skiplist, ps_entry);
+ }
+ skip_append(block, i, skiplist, por);
+ }
+ }
+
+ TAILQ_FOREACH(por, &block->sb_rules, por_entry)
+ rule_count++;
+
+ /*
+ * Now we're going to ignore any fields that are identical between
+ * all of the rules in the superblock and those fields which differ
+ * between every rule in the superblock.
+ */
+ largest = 0;
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (skiplist->ps_count == rule_count) {
+ DEBUG("(%d) original skipstep '%s' is all rules",
+ depth, skip_comparitors_names[i]);
+ skiplist->ps_count = 0;
+ } else if (skiplist->ps_count == 1) {
+ skiplist->ps_count = 0;
+ } else {
+ DEBUG("(%d) original skipstep '%s' largest jump is %d",
+ depth, skip_comparitors_names[i],
+ skiplist->ps_count);
+ if (skiplist->ps_count > largest)
+ largest = skiplist->ps_count;
+ }
+ }
+ if (largest == 0) {
+ /* Ugh. There is NO commonality in the superblock on which
+ * optimize the skipsteps optimization.
+ */
+ goto done;
+ }
+
+ /*
+ * Now we're going to empty the superblock rule list and re-create
+ * it based on a more optimal skipstep order.
+ */
+ TAILQ_INIT(&head);
+ TAILQ_CONCAT(&head, &block->sb_rules, por_entry);
+
+ while (!TAILQ_EMPTY(&head)) {
+ largest = 1;
+
+ /*
+ * Find the most useful skip steps remaining
+ */
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (skiplist->ps_count > largest) {
+ largest = skiplist->ps_count;
+ largest_list = i;
+ }
+ }
+
+ if (largest <= 1) {
+ /*
+ * Nothing useful left. Leave remaining rules in order.
+ */
+ DEBUG("(%d) no more commonality for skip steps", depth);
+ TAILQ_CONCAT(&block->sb_rules, &head, por_entry);
+ } else {
+ /*
+ * There is commonality. Extract those common rules
+ * and place them in the ruleset adjacent to each
+ * other.
+ */
+ skiplist = TAILQ_FIRST(&block->sb_skipsteps[
+ largest_list]);
+ DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d",
+ depth, skip_comparitors_names[largest_list],
+ largest, TAILQ_FIRST(&TAILQ_FIRST(&block->
+ sb_skipsteps [largest_list])->ps_rules)->
+ por_rule.nr);
+ TAILQ_REMOVE(&block->sb_skipsteps[largest_list],
+ skiplist, ps_entry);
+
+
+ /*
+ * There may be further commonality inside these
+ * rules. So we'll split them off into they're own
+ * superblock and pass it back into the optimizer.
+ */
+ if (skiplist->ps_count > 2) {
+ if ((newblock = calloc(1, sizeof(*newblock)))
+ == NULL) {
+ warn("calloc");
+ return (1);
+ }
+ TAILQ_INIT(&newblock->sb_rules);
+ for (i = 0; i < PF_SKIP_COUNT; i++)
+ TAILQ_INIT(&newblock->sb_skipsteps[i]);
+ TAILQ_INSERT_BEFORE(block, newblock, sb_entry);
+ DEBUG("(%d) splitting off %d rules from superblock @ #%d",
+ depth, skiplist->ps_count,
+ TAILQ_FIRST(&skiplist->ps_rules)->
+ por_rule.nr);
+ } else {
+ newblock = block;
+ }
+
+ while ((por = TAILQ_FIRST(&skiplist->ps_rules))) {
+ TAILQ_REMOVE(&head, por, por_entry);
+ TAILQ_REMOVE(&skiplist->ps_rules, por,
+ por_skip_entry[largest_list]);
+ TAILQ_INSERT_TAIL(&newblock->sb_rules, por,
+ por_entry);
+
+ /* Remove this rule from all other skiplists */
+ remove_from_skipsteps(&block->sb_skipsteps[
+ largest_list], block, por, skiplist);
+ }
+ free(skiplist);
+ if (newblock != block)
+ if (reorder_rules(pf, newblock, depth + 1))
+ return (1);
+ }
+ }
+
+done:
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) {
+ TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist,
+ ps_entry);
+ free(skiplist);
+ }
+ }
+
+ return (0);
+}
+
+
+/*
+ * Optimization pass #4: re-order 'quick' rules based on feedback from the
+ * currently running ruleset
+ */
+int
+block_feedback(struct pfctl *pf, struct superblock *block)
+{
+ TAILQ_HEAD( , pf_opt_rule) queue;
+ struct pf_opt_rule *por1, *por2;
+ struct pfctl_rule a, b;
+
+
+ /*
+ * Walk through all of the profiled superblock's rules and copy
+ * the counters onto our rules.
+ */
+ TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) {
+ comparable_rule(&a, &por1->por_rule, DC);
+ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
+ if (por2->por_profile_count)
+ continue;
+ comparable_rule(&b, &por2->por_rule, DC);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ por2->por_profile_count =
+ por1->por_rule.packets[0] +
+ por1->por_rule.packets[1];
+ break;
+ }
+ }
+ }
+ superblock_free(pf, block->sb_profiled_block);
+ block->sb_profiled_block = NULL;
+
+ /*
+ * Now we pull all of the rules off the superblock and re-insert them
+ * in sorted order.
+ */
+
+ TAILQ_INIT(&queue);
+ TAILQ_CONCAT(&queue, &block->sb_rules, por_entry);
+
+ while ((por1 = TAILQ_FIRST(&queue)) != NULL) {
+ TAILQ_REMOVE(&queue, por1, por_entry);
+/* XXX I should sort all of the unused rules based on skip steps */
+ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) {
+ if (por1->por_profile_count > por2->por_profile_count) {
+ TAILQ_INSERT_BEFORE(por2, por1, por_entry);
+ break;
+ }
+ }
+#ifdef __FreeBSD__
+ if (por2 == NULL)
+#else
+ if (por2 == TAILQ_END(&block->sb_rules))
+#endif
+ TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Load the current ruleset from the kernel and try to associate them with
+ * the ruleset we're optimizing.
+ */
+int
+load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks)
+{
+ char anchor_call[MAXPATHLEN] = "";
+ struct superblock *block, *blockcur;
+ struct superblocks prof_superblocks;
+ struct pf_opt_rule *por;
+ struct pf_opt_queue queue;
+ struct pfctl_rules_info rules;
+ struct pfctl_rule a, b, rule;
+ int nr, mnr, ret;
+
+ TAILQ_INIT(&queue);
+ TAILQ_INIT(&prof_superblocks);
+
+ if ((ret = pfctl_get_rules_info_h(pf->h, &rules, PF_PASS, "")) != 0) {
+ warnx("%s", pf_strerror(ret));
+ return (1);
+ }
+ mnr = rules.nr;
+
+ DEBUG("Loading %d active rules for a feedback profile", mnr);
+ for (nr = 0; nr < mnr; ++nr) {
+ struct pfctl_ruleset *rs;
+ if ((por = calloc(1, sizeof(*por))) == NULL) {
+ warn("calloc");
+ return (1);
+ }
+
+ if (pfctl_get_rule_h(pf->h, nr, rules.ticket, "", PF_PASS,
+ &rule, anchor_call)) {
+ warnx("%s", pf_strerror(ret));
+ free(por);
+ return (1);
+ }
+ memcpy(&por->por_rule, &rule, sizeof(por->por_rule));
+ rs = pf_find_or_create_ruleset(anchor_call);
+ por->por_rule.anchor = rs->anchor;
+ if (TAILQ_EMPTY(&por->por_rule.rdr.list))
+ memset(&por->por_rule.rdr, 0,
+ sizeof(por->por_rule.rdr));
+ if (TAILQ_EMPTY(&por->por_rule.nat.list))
+ memset(&por->por_rule.nat, 0,
+ sizeof(por->por_rule.nat));
+ TAILQ_INSERT_TAIL(&queue, por, por_entry);
+
+ /* XXX pfctl_get_pool(pf->dev, &rule.rdr, nr, pr.ticket,
+ * PF_PASS, pf->anchor) ???
+ * ... pfctl_clear_pool(&rule.rdr)
+ */
+ }
+
+ if (construct_superblocks(pf, &queue, &prof_superblocks))
+ return (1);
+
+
+ /*
+ * Now we try to associate the active ruleset's superblocks with
+ * the superblocks we're compiling.
+ */
+ block = TAILQ_FIRST(superblocks);
+ blockcur = TAILQ_FIRST(&prof_superblocks);
+ while (block && blockcur) {
+ comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule,
+ BREAK);
+ comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule,
+ BREAK);
+ if (memcmp(&a, &b, sizeof(a)) == 0) {
+ /* The two superblocks lined up */
+ block->sb_profiled_block = blockcur;
+ } else {
+ DEBUG("superblocks don't line up between #%d and #%d",
+ TAILQ_FIRST(&block->sb_rules)->por_rule.nr,
+ TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr);
+ break;
+ }
+ block = TAILQ_NEXT(block, sb_entry);
+ blockcur = TAILQ_NEXT(blockcur, sb_entry);
+ }
+
+
+
+ /* Free any superblocks we couldn't link */
+ while (blockcur) {
+ block = TAILQ_NEXT(blockcur, sb_entry);
+ superblock_free(pf, blockcur);
+ blockcur = block;
+ }
+ return (0);
+}
+
+
+/*
+ * Compare a rule to a skiplist to see if the rule is a member
+ */
+int
+skip_compare(int skipnum, struct pf_skip_step *skiplist,
+ struct pf_opt_rule *por)
+{
+ struct pfctl_rule *a, *b;
+ if (skipnum >= PF_SKIP_COUNT || skipnum < 0)
+ errx(1, "skip_compare() out of bounds");
+ a = &por->por_rule;
+ b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule;
+
+ return ((skip_comparitors[skipnum])(a, b));
+}
+
+
+/*
+ * Add a rule to a skiplist
+ */
+void
+skip_append(struct superblock *superblock, int skipnum,
+ struct pf_skip_step *skiplist, struct pf_opt_rule *por)
+{
+ struct pf_skip_step *prev;
+
+ skiplist->ps_count++;
+ TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]);
+
+ /* Keep the list of skiplists sorted by whichever is larger */
+ while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) &&
+ prev->ps_count < skiplist->ps_count) {
+ TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum],
+ skiplist, ps_entry);
+ TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry);
+ }
+}
+
+
+/*
+ * Remove a rule from the other skiplist calculations.
+ */
+void
+remove_from_skipsteps(struct skiplist *head, struct superblock *block,
+ struct pf_opt_rule *por, struct pf_skip_step *active_list)
+{
+ struct pf_skip_step *sk, *next;
+ struct pf_opt_rule *p2;
+ int i, found;
+
+ for (i = 0; i < PF_SKIP_COUNT; i++) {
+ sk = TAILQ_FIRST(&block->sb_skipsteps[i]);
+ if (sk == NULL || sk == active_list || sk->ps_count <= 1)
+ continue;
+ found = 0;
+ do {
+ TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i])
+ if (p2 == por) {
+ TAILQ_REMOVE(&sk->ps_rules, p2,
+ por_skip_entry[i]);
+ found = 1;
+ sk->ps_count--;
+ break;
+ }
+ } while (!found && (sk = TAILQ_NEXT(sk, ps_entry)));
+ if (found && sk) {
+ /* Does this change the sorting order? */
+ while ((next = TAILQ_NEXT(sk, ps_entry)) &&
+ next->ps_count > sk->ps_count) {
+ TAILQ_REMOVE(head, sk, ps_entry);
+ TAILQ_INSERT_AFTER(head, next, sk, ps_entry);
+ }
+#ifdef OPT_DEBUG
+ next = TAILQ_NEXT(sk, ps_entry);
+ assert(next == NULL || next->ps_count <= sk->ps_count);
+#endif /* OPT_DEBUG */
+ }
+ }
+}
+
+
+/* Compare two rules AF field for skiplist construction */
+int
+skip_cmp_af(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (a->af != b->af || a->af == 0)
+ return (1);
+ return (0);
+}
+
+/* Compare two rules DIRECTION field for skiplist construction */
+int
+skip_cmp_dir(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (a->direction == 0 || a->direction != b->direction)
+ return (1);
+ return (0);
+}
+
+/* Compare two rules DST Address field for skiplist construction */
+int
+skip_cmp_dst_addr(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (a->dst.neg != b->dst.neg ||
+ a->dst.addr.type != b->dst.addr.type)
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ switch (a->dst.addr.type) {
+ case PF_ADDR_ADDRMASK:
+ if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr,
+ sizeof(a->dst.addr.v.a.addr)) ||
+ memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
+ sizeof(a->dst.addr.v.a.mask)) ||
+ (a->dst.addr.v.a.addr.addr32[0] == 0 &&
+ a->dst.addr.v.a.addr.addr32[1] == 0 &&
+ a->dst.addr.v.a.addr.addr32[2] == 0 &&
+ a->dst.addr.v.a.addr.addr32[3] == 0))
+ return (1);
+ return (0);
+ case PF_ADDR_DYNIFTL:
+ if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 ||
+ a->dst.addr.iflags != b->dst.addr.iflags ||
+ memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask,
+ sizeof(a->dst.addr.v.a.mask)))
+ return (1);
+ return (0);
+ case PF_ADDR_NOROUTE:
+ case PF_ADDR_URPFFAILED:
+ return (0);
+ case PF_ADDR_TABLE:
+ return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname));
+ }
+ return (1);
+}
+
+/* Compare two rules DST port field for skiplist construction */
+int
+skip_cmp_dst_port(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op ||
+ a->dst.port[0] != b->dst.port[0] ||
+ a->dst.port[1] != b->dst.port[1])
+ return (1);
+ return (0);
+}
+
+/* Compare two rules IFP field for skiplist construction */
+int
+skip_cmp_ifp(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0')
+ return (1);
+ return (a->ifnot != b->ifnot);
+}
+
+/* Compare two rules PROTO field for skiplist construction */
+int
+skip_cmp_proto(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ return (a->proto != b->proto || a->proto == 0);
+}
+
+/* Compare two rules SRC addr field for skiplist construction */
+int
+skip_cmp_src_addr(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (a->src.neg != b->src.neg ||
+ a->src.addr.type != b->src.addr.type)
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ switch (a->src.addr.type) {
+ case PF_ADDR_ADDRMASK:
+ if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr,
+ sizeof(a->src.addr.v.a.addr)) ||
+ memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
+ sizeof(a->src.addr.v.a.mask)) ||
+ (a->src.addr.v.a.addr.addr32[0] == 0 &&
+ a->src.addr.v.a.addr.addr32[1] == 0 &&
+ a->src.addr.v.a.addr.addr32[2] == 0 &&
+ a->src.addr.v.a.addr.addr32[3] == 0))
+ return (1);
+ return (0);
+ case PF_ADDR_DYNIFTL:
+ if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 ||
+ a->src.addr.iflags != b->src.addr.iflags ||
+ memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask,
+ sizeof(a->src.addr.v.a.mask)))
+ return (1);
+ return (0);
+ case PF_ADDR_NOROUTE:
+ case PF_ADDR_URPFFAILED:
+ return (0);
+ case PF_ADDR_TABLE:
+ return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname));
+ }
+ return (1);
+}
+
+/* Compare two rules SRC port field for skiplist construction */
+int
+skip_cmp_src_port(struct pfctl_rule *a, struct pfctl_rule *b)
+{
+ if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op ||
+ a->src.port[0] != b->src.port[0] ||
+ a->src.port[1] != b->src.port[1])
+ return (1);
+ /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0
+ * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP ||
+ * a->proto == IPPROTO_ICMP
+ * return (1);
+ */
+ return (0);
+}
+
+
+void
+skip_init(void)
+{
+ struct {
+ char *name;
+ int skipnum;
+ int (*func)(struct pfctl_rule *, struct pfctl_rule *);
+ } comps[] = PF_SKIP_COMPARITORS;
+ int skipnum, i;
+
+ for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) {
+ for (i = 0; i < sizeof(comps)/sizeof(*comps); i++)
+ if (comps[i].skipnum == skipnum) {
+ skip_comparitors[skipnum] = comps[i].func;
+ skip_comparitors_names[skipnum] = comps[i].name;
+ }
+ }
+ for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++)
+ if (skip_comparitors[skipnum] == NULL)
+ errx(1, "Need to add skip step comparitor to pfctl?!");
+}
+
+/*
+ * Add a host/netmask to a table
+ */
+int
+add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af,
+ struct pf_rule_addr *addr)
+{
+#ifdef OPT_DEBUG
+ char buf[128];
+#endif /* OPT_DEBUG */
+ static int tablenum = 0;
+ struct node_host node_host;
+
+ if (*tbl == NULL) {
+ if ((*tbl = calloc(1, sizeof(**tbl))) == NULL ||
+ ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) ==
+ NULL)
+ err(1, "calloc");
+ (*tbl)->pt_refcnt = 1;
+ (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS;
+ SIMPLEQ_INIT(&(*tbl)->pt_nodes);
+
+ /* This is just a temporary table name */
+ snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d",
+ PF_OPTIMIZER_TABLE_PFX, tablenum++);
+ DEBUG("creating table <%s>", (*tbl)->pt_name);
+ }
+
+ memset(&node_host, 0, sizeof(node_host));
+ node_host.af = af;
+ node_host.addr = addr->addr;
+
+#ifdef OPT_DEBUG
+ DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af,
+ &node_host.addr.v.a.addr, buf, sizeof(buf)),
+ unmask(&node_host.addr.v.a.mask));
+#endif /* OPT_DEBUG */
+
+ if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) {
+ warn("failed to add host");
+ return (1);
+ }
+ if (pf->opts & PF_OPT_VERBOSE) {
+ struct node_tinit *ti;
+
+ if ((ti = calloc(1, sizeof(*ti))) == NULL)
+ err(1, "malloc");
+ if ((ti->host = malloc(sizeof(*ti->host))) == NULL)
+ err(1, "malloc");
+ memcpy(ti->host, &node_host, sizeof(*ti->host));
+ SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries);
+ }
+
+ (*tbl)->pt_rulecount++;
+ if ((*tbl)->pt_rulecount == TABLE_THRESHOLD)
+ DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name);
+
+ return (0);
+}
+
+
+/*
+ * Do the dirty work of choosing an unused table name and creating it.
+ * (be careful with the table name, it might already be used in another anchor)
+ */
+int
+pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl)
+{
+ static int tablenum;
+ struct pfr_table *t;
+
+ if (table_buffer.pfrb_type == 0) {
+ /* Initialize the list of tables */
+ table_buffer.pfrb_type = PFRB_TABLES;
+ for (;;) {
+ pfr_buf_grow(&table_buffer, table_buffer.pfrb_size);
+ table_buffer.pfrb_size = table_buffer.pfrb_msize;
+ if (pfr_get_tables(NULL, table_buffer.pfrb_caddr,
+ &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS))
+ err(1, "pfr_get_tables");
+ if (table_buffer.pfrb_size <= table_buffer.pfrb_msize)
+ break;
+ }
+ table_identifier = arc4random();
+ }
+
+ /* XXX would be *really* nice to avoid duplicating identical tables */
+
+ /* Now we have to pick a table name that isn't used */
+again:
+ DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name,
+ PF_OPTIMIZER_TABLE_PFX, table_identifier, tablenum);
+ snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d",
+ PF_OPTIMIZER_TABLE_PFX, table_identifier, tablenum);
+ PFRB_FOREACH(t, &table_buffer) {
+ if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) {
+ /* Collision. Try again */
+ DEBUG("wow, table <%s> in use. trying again",
+ tbl->pt_name);
+ table_identifier = arc4random();
+ goto again;
+ }
+ }
+ tablenum++;
+
+
+ if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1,
+ pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket,
+ NULL)) {
+ warn("failed to create table %s in %s",
+ tbl->pt_name, pf->astack[0]->name);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * Partition the flat ruleset into a list of distinct superblocks
+ */
+int
+construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue,
+ struct superblocks *superblocks)
+{
+ struct superblock *block = NULL;
+ struct pf_opt_rule *por;
+ int i;
+
+ while (!TAILQ_EMPTY(opt_queue)) {
+ por = TAILQ_FIRST(opt_queue);
+ TAILQ_REMOVE(opt_queue, por, por_entry);
+ if (block == NULL || !superblock_inclusive(block, por)) {
+ if ((block = calloc(1, sizeof(*block))) == NULL) {
+ warn("calloc");
+ return (1);
+ }
+ TAILQ_INIT(&block->sb_rules);
+ for (i = 0; i < PF_SKIP_COUNT; i++)
+ TAILQ_INIT(&block->sb_skipsteps[i]);
+ TAILQ_INSERT_TAIL(superblocks, block, sb_entry);
+ }
+ TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry);
+ }
+
+ return (0);
+}
+
+
+/*
+ * Compare two rule addresses
+ */
+int
+addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b)
+{
+ if (a->neg != b->neg)
+ return (0);
+ return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0);
+}
+
+
+/*
+ * The addresses are not equal, but can we combine them into one table?
+ */
+int
+addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b)
+{
+ if (a->addr.type != PF_ADDR_ADDRMASK ||
+ b->addr.type != PF_ADDR_ADDRMASK)
+ return (0);
+ if (a->neg != b->neg || a->port_op != b->port_op ||
+ a->port[0] != b->port[0] || a->port[1] != b->port[1])
+ return (0);
+ return (1);
+}
+
+
+/*
+ * Are we allowed to combine these two rules
+ */
+int
+rules_combineable(struct pfctl_rule *p1, struct pfctl_rule *p2)
+{
+ struct pfctl_rule a, b;
+
+ comparable_rule(&a, p1, COMBINED);
+ comparable_rule(&b, p2, COMBINED);
+ return (memcmp(&a, &b, sizeof(a)) == 0);
+}
+
+
+/*
+ * Can a rule be included inside a superblock
+ */
+int
+superblock_inclusive(struct superblock *block, struct pf_opt_rule *por)
+{
+ struct pfctl_rule a, b;
+ int i, j;
+
+ /* First check for hard breaks */
+ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) {
+ if (pf_rule_desc[i].prf_type == BARRIER) {
+ for (j = 0; j < pf_rule_desc[i].prf_size; j++)
+ if (((char *)&por->por_rule)[j +
+ pf_rule_desc[i].prf_offset] != 0)
+ return (0);
+ }
+ }
+
+ /* per-rule src-track is also a hard break */
+ if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK)
+ return (0);
+
+ /*
+ * Have to handle interface groups separately. Consider the following
+ * rules:
+ * block on EXTIFS to any port 22
+ * pass on em0 to any port 22
+ * (where EXTIFS is an arbitrary interface group)
+ * The optimizer may decide to re-order the pass rule in front of the
+ * block rule. But what if EXTIFS includes em0??? Such a reordering
+ * would change the meaning of the ruleset.
+ * We can't just lookup the EXTIFS group and check if em0 is a member
+ * because the user is allowed to add interfaces to a group during
+ * runtime.
+ * Ergo interface groups become a defacto superblock break :-(
+ */
+ if (interface_group(por->por_rule.ifname) ||
+ interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) {
+ if (strcasecmp(por->por_rule.ifname,
+ TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0)
+ return (0);
+ }
+
+ comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE);
+ comparable_rule(&b, &por->por_rule, NOMERGE);
+ if (memcmp(&a, &b, sizeof(a)) == 0)
+ return (1);
+
+#ifdef OPT_DEBUG
+ for (i = 0; i < sizeof(por->por_rule); i++) {
+ int closest = -1;
+ if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) {
+ for (j = 0; j < sizeof(pf_rule_desc) /
+ sizeof(*pf_rule_desc); j++) {
+ if (i >= pf_rule_desc[j].prf_offset &&
+ i < pf_rule_desc[j].prf_offset +
+ pf_rule_desc[j].prf_size) {
+ DEBUG("superblock break @ %d due to %s",
+ por->por_rule.nr,
+ pf_rule_desc[j].prf_name);
+ return (0);
+ }
+ if (i > pf_rule_desc[j].prf_offset) {
+ if (closest == -1 ||
+ i-pf_rule_desc[j].prf_offset <
+ i-pf_rule_desc[closest].prf_offset)
+ closest = j;
+ }
+ }
+
+ if (closest >= 0)
+ DEBUG("superblock break @ %d on %s+%zxh",
+ por->por_rule.nr,
+ pf_rule_desc[closest].prf_name,
+ i - pf_rule_desc[closest].prf_offset -
+ pf_rule_desc[closest].prf_size);
+ else
+ DEBUG("superblock break @ %d on field @ %d",
+ por->por_rule.nr, i);
+ return (0);
+ }
+ }
+#endif /* OPT_DEBUG */
+
+ return (0);
+}
+
+
+/*
+ * Figure out if an interface name is an actual interface or actually a
+ * group of interfaces.
+ */
+int
+interface_group(const char *ifname)
+{
+ int s;
+ struct ifgroupreq ifgr;
+
+ if (ifname == NULL || !ifname[0])
+ return (0);
+
+ s = get_query_socket();
+
+ memset(&ifgr, 0, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ);
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) {
+ if (errno == ENOENT)
+ return (0);
+ else
+ err(1, "SIOCGIFGMEMB");
+ }
+
+ return (1);
+}
+
+
+/*
+ * Make a rule that can directly compared by memcmp()
+ */
+void
+comparable_rule(struct pfctl_rule *dst, const struct pfctl_rule *src, int type)
+{
+ int i;
+ /*
+ * To simplify the comparison, we just zero out the fields that are
+ * allowed to be different and then do a simple memcmp()
+ */
+ memcpy(dst, src, sizeof(*dst));
+ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++)
+ if (pf_rule_desc[i].prf_type >= type) {
+#ifdef OPT_DEBUG
+ assert(pf_rule_desc[i].prf_type != NEVER ||
+ *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0);
+#endif /* OPT_DEBUG */
+ memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0,
+ pf_rule_desc[i].prf_size);
+ }
+}
+
+
+/*
+ * Remove superset information from two rules so we can directly compare them
+ * with memcmp()
+ */
+void
+exclude_supersets(struct pfctl_rule *super, struct pfctl_rule *sub)
+{
+ if (super->ifname[0] == '\0')
+ memset(sub->ifname, 0, sizeof(sub->ifname));
+ if (super->direction == PF_INOUT)
+ sub->direction = PF_INOUT;
+ if ((super->proto == 0 || super->proto == sub->proto) &&
+ super->flags == 0 && super->flagset == 0 && (sub->flags ||
+ sub->flagset)) {
+ sub->flags = super->flags;
+ sub->flagset = super->flagset;
+ }
+ if (super->proto == 0)
+ sub->proto = 0;
+
+ if (super->src.port_op == 0) {
+ sub->src.port_op = 0;
+ sub->src.port[0] = 0;
+ sub->src.port[1] = 0;
+ }
+ if (super->dst.port_op == 0) {
+ sub->dst.port_op = 0;
+ sub->dst.port[0] = 0;
+ sub->dst.port[1] = 0;
+ }
+
+ if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg &&
+ !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 &&
+ super->src.addr.v.a.mask.addr32[1] == 0 &&
+ super->src.addr.v.a.mask.addr32[2] == 0 &&
+ super->src.addr.v.a.mask.addr32[3] == 0)
+ memset(&sub->src.addr, 0, sizeof(sub->src.addr));
+ else if (super->src.addr.type == PF_ADDR_ADDRMASK &&
+ sub->src.addr.type == PF_ADDR_ADDRMASK &&
+ super->src.neg == sub->src.neg &&
+ super->af == sub->af &&
+ unmask(&super->src.addr.v.a.mask) <
+ unmask(&sub->src.addr.v.a.mask) &&
+ super->src.addr.v.a.addr.addr32[0] ==
+ (sub->src.addr.v.a.addr.addr32[0] &
+ super->src.addr.v.a.mask.addr32[0]) &&
+ super->src.addr.v.a.addr.addr32[1] ==
+ (sub->src.addr.v.a.addr.addr32[1] &
+ super->src.addr.v.a.mask.addr32[1]) &&
+ super->src.addr.v.a.addr.addr32[2] ==
+ (sub->src.addr.v.a.addr.addr32[2] &
+ super->src.addr.v.a.mask.addr32[2]) &&
+ super->src.addr.v.a.addr.addr32[3] ==
+ (sub->src.addr.v.a.addr.addr32[3] &
+ super->src.addr.v.a.mask.addr32[3])) {
+ /* sub->src.addr is a subset of super->src.addr/mask */
+ memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr));
+ }
+
+ if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg &&
+ !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 &&
+ super->dst.addr.v.a.mask.addr32[1] == 0 &&
+ super->dst.addr.v.a.mask.addr32[2] == 0 &&
+ super->dst.addr.v.a.mask.addr32[3] == 0)
+ memset(&sub->dst.addr, 0, sizeof(sub->dst.addr));
+ else if (super->dst.addr.type == PF_ADDR_ADDRMASK &&
+ sub->dst.addr.type == PF_ADDR_ADDRMASK &&
+ super->dst.neg == sub->dst.neg &&
+ super->af == sub->af &&
+ unmask(&super->dst.addr.v.a.mask) <
+ unmask(&sub->dst.addr.v.a.mask) &&
+ super->dst.addr.v.a.addr.addr32[0] ==
+ (sub->dst.addr.v.a.addr.addr32[0] &
+ super->dst.addr.v.a.mask.addr32[0]) &&
+ super->dst.addr.v.a.addr.addr32[1] ==
+ (sub->dst.addr.v.a.addr.addr32[1] &
+ super->dst.addr.v.a.mask.addr32[1]) &&
+ super->dst.addr.v.a.addr.addr32[2] ==
+ (sub->dst.addr.v.a.addr.addr32[2] &
+ super->dst.addr.v.a.mask.addr32[2]) &&
+ super->dst.addr.v.a.addr.addr32[3] ==
+ (sub->dst.addr.v.a.addr.addr32[3] &
+ super->dst.addr.v.a.mask.addr32[3])) {
+ /* sub->dst.addr is a subset of super->dst.addr/mask */
+ memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr));
+ }
+
+ if (super->af == 0)
+ sub->af = 0;
+}
+
+
+void
+superblock_free(struct pfctl *pf, struct superblock *block)
+{
+ struct pf_opt_rule *por;
+ while ((por = TAILQ_FIRST(&block->sb_rules))) {
+ TAILQ_REMOVE(&block->sb_rules, por, por_entry);
+ pf_opt_table_unref(por->por_src_tbl);
+ pf_opt_table_unref(por->por_dst_tbl);
+ free(por);
+ }
+ if (block->sb_profiled_block)
+ superblock_free(pf, block->sb_profiled_block);
+ free(block);
+}
+
+struct pf_opt_tbl *
+pf_opt_table_ref(struct pf_opt_tbl *pt)
+{
+ /* parser does not run concurrently, we don't need atomic ops. */
+ if (pt != NULL)
+ pt->pt_refcnt++;
+
+ return (pt);
+}
+
+void
+pf_opt_table_unref(struct pf_opt_tbl *pt)
+{
+ if ((pt != NULL) && ((--pt->pt_refcnt) == 0)) {
+ if (pt->pt_buf != NULL) {
+ pfr_buf_clear(pt->pt_buf);
+ free(pt->pt_buf);
+ }
+ free(pt);
+ }
+}
diff --git a/sbin/pfctl/pfctl_osfp.c b/sbin/pfctl/pfctl_osfp.c
new file mode 100644
index 000000000000..5770c8343a46
--- /dev/null
+++ b/sbin/pfctl/pfctl_osfp.c
@@ -0,0 +1,1098 @@
+/* $OpenBSD: pfctl_osfp.c,v 1.14 2006/04/08 02:13:14 ray Exp $ */
+
+/*
+ * Copyright (c) 2003 Mike Frantzen <frantzen@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+#ifndef MIN
+# define MIN(a,b) (((a) < (b)) ? (a) : (b))
+#endif /* MIN */
+#ifndef MAX
+# define MAX(a,b) (((a) > (b)) ? (a) : (b))
+#endif /* MAX */
+
+
+#if 0
+# define DEBUG(fp, str, v...) \
+ fprintf(stderr, "%s:%s:%s " str "\n", (fp)->fp_os.fp_class_nm, \
+ (fp)->fp_os.fp_version_nm, (fp)->fp_os.fp_subtype_nm , ## v);
+#else
+# define DEBUG(fp, str, v...) ((void)0)
+#endif
+
+
+struct name_entry;
+LIST_HEAD(name_list, name_entry);
+struct name_entry {
+ LIST_ENTRY(name_entry) nm_entry;
+ int nm_num;
+ char nm_name[PF_OSFP_LEN];
+
+ struct name_list nm_sublist;
+ int nm_sublist_num;
+};
+static struct name_list classes = LIST_HEAD_INITIALIZER(&classes);
+static int class_count;
+static int fingerprint_count;
+
+void add_fingerprint(int, int, struct pf_osfp_ioctl *);
+struct name_entry *fingerprint_name_entry(struct name_list *, char *);
+void pfctl_flush_my_fingerprints(struct name_list *);
+char *get_field(char **, size_t *, int *);
+int get_int(char **, size_t *, int *, int *, const char *,
+ int, int, const char *, int);
+int get_str(char **, size_t *, char **, const char *, int,
+ const char *, int);
+int get_tcpopts(const char *, int, const char *,
+ pf_tcpopts_t *, int *, int *, int *, int *, int *,
+ int *);
+void import_fingerprint(struct pf_osfp_ioctl *);
+const char *print_ioctl(struct pf_osfp_ioctl *);
+void print_name_list(int, struct name_list *, const char *);
+void sort_name_list(int, struct name_list *);
+struct name_entry *lookup_name_list(struct name_list *, const char *);
+
+/* Load fingerprints from a file */
+int
+pfctl_file_fingerprints(int dev, int opts, const char *fp_filename)
+{
+ FILE *in;
+ char *line;
+ size_t len;
+ int i, lineno = 0;
+ int window, w_mod, ttl, df, psize, p_mod, mss, mss_mod, wscale,
+ wscale_mod, optcnt, ts0;
+ pf_tcpopts_t packed_tcpopts;
+ char *class, *version, *subtype, *desc, *tcpopts;
+ struct pf_osfp_ioctl fp;
+
+ pfctl_flush_my_fingerprints(&classes);
+
+ if ((in = pfctl_fopen(fp_filename, "r")) == NULL) {
+ warn("%s", fp_filename);
+ return (1);
+ }
+ class = version = subtype = desc = tcpopts = NULL;
+
+ if ((opts & PF_OPT_NOACTION) == 0)
+ pfctl_clear_fingerprints(dev, opts);
+
+ while ((line = fgetln(in, &len)) != NULL) {
+ lineno++;
+ free(class);
+ free(version);
+ free(subtype);
+ free(desc);
+ free(tcpopts);
+ class = version = subtype = desc = tcpopts = NULL;
+ memset(&fp, 0, sizeof(fp));
+
+ /* Chop off comment */
+ for (i = 0; i < len; i++)
+ if (line[i] == '#') {
+ len = i;
+ break;
+ }
+ /* Chop off whitespace */
+ while (len > 0 && isspace(line[len - 1]))
+ len--;
+ while (len > 0 && isspace(line[0])) {
+ len--;
+ line++;
+ }
+ if (len == 0)
+ continue;
+
+#define T_DC 0x01 /* Allow don't care */
+#define T_MSS 0x02 /* Allow MSS multiple */
+#define T_MTU 0x04 /* Allow MTU multiple */
+#define T_MOD 0x08 /* Allow modulus */
+
+#define GET_INT(v, mod, n, ty, mx) \
+ get_int(&line, &len, &v, mod, n, ty, mx, fp_filename, lineno)
+#define GET_STR(v, n, mn) \
+ get_str(&line, &len, &v, n, mn, fp_filename, lineno)
+
+ if (GET_INT(window, &w_mod, "window size", T_DC|T_MSS|T_MTU|
+ T_MOD, 0xffff) ||
+ GET_INT(ttl, NULL, "ttl", 0, 0xff) ||
+ GET_INT(df, NULL, "don't fragment frag", 0, 1) ||
+ GET_INT(psize, &p_mod, "overall packet size", T_MOD|T_DC,
+ 8192) ||
+ GET_STR(tcpopts, "TCP Options", 1) ||
+ GET_STR(class, "OS class", 1) ||
+ GET_STR(version, "OS version", 0) ||
+ GET_STR(subtype, "OS subtype", 0) ||
+ GET_STR(desc, "OS description", 2))
+ continue;
+ if (get_tcpopts(fp_filename, lineno, tcpopts, &packed_tcpopts,
+ &optcnt, &mss, &mss_mod, &wscale, &wscale_mod, &ts0))
+ continue;
+ if (len != 0) {
+ fprintf(stderr, "%s:%d excess field\n", fp_filename,
+ lineno);
+ continue;
+ }
+
+ fp.fp_ttl = ttl;
+ if (df)
+ fp.fp_flags |= PF_OSFP_DF;
+ switch (w_mod) {
+ case 0:
+ break;
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_WSIZE_DC;
+ break;
+ case T_MSS:
+ fp.fp_flags |= PF_OSFP_WSIZE_MSS;
+ break;
+ case T_MTU:
+ fp.fp_flags |= PF_OSFP_WSIZE_MTU;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_WSIZE_MOD;
+ break;
+ }
+ fp.fp_wsize = window;
+
+ switch (p_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_PSIZE_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_PSIZE_MOD;
+ }
+ fp.fp_psize = psize;
+
+
+ switch (wscale_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_WSCALE_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_WSCALE_MOD;
+ }
+ fp.fp_wscale = wscale;
+
+ switch (mss_mod) {
+ case T_DC:
+ fp.fp_flags |= PF_OSFP_MSS_DC;
+ break;
+ case T_MOD:
+ fp.fp_flags |= PF_OSFP_MSS_MOD;
+ break;
+ }
+ fp.fp_mss = mss;
+
+ fp.fp_tcpopts = packed_tcpopts;
+ fp.fp_optcnt = optcnt;
+ if (ts0)
+ fp.fp_flags |= PF_OSFP_TS0;
+
+ if (class[0] == '@')
+ fp.fp_os.fp_enflags |= PF_OSFP_GENERIC;
+ if (class[0] == '*')
+ fp.fp_os.fp_enflags |= PF_OSFP_NODETAIL;
+
+ if (class[0] == '@' || class[0] == '*')
+ strlcpy(fp.fp_os.fp_class_nm, class + 1,
+ sizeof(fp.fp_os.fp_class_nm));
+ else
+ strlcpy(fp.fp_os.fp_class_nm, class,
+ sizeof(fp.fp_os.fp_class_nm));
+ strlcpy(fp.fp_os.fp_version_nm, version,
+ sizeof(fp.fp_os.fp_version_nm));
+ strlcpy(fp.fp_os.fp_subtype_nm, subtype,
+ sizeof(fp.fp_os.fp_subtype_nm));
+
+ add_fingerprint(dev, opts, &fp);
+
+ fp.fp_flags |= (PF_OSFP_DF | PF_OSFP_INET6);
+ fp.fp_psize += sizeof(struct ip6_hdr) - sizeof(struct ip);
+ add_fingerprint(dev, opts, &fp);
+ }
+
+ free(class);
+ free(version);
+ free(subtype);
+ free(desc);
+ free(tcpopts);
+
+ fclose(in);
+
+ if (opts & PF_OPT_VERBOSE2)
+ printf("Loaded %d passive OS fingerprints\n",
+ fingerprint_count);
+ return (0);
+}
+
+/* flush the kernel's fingerprints */
+void
+pfctl_clear_fingerprints(int dev, int opts)
+{
+ if (ioctl(dev, DIOCOSFPFLUSH))
+ pfctl_err(opts, 1, "DIOCOSFPFLUSH");
+}
+
+/* flush pfctl's view of the fingerprints */
+void
+pfctl_flush_my_fingerprints(struct name_list *list)
+{
+ struct name_entry *nm;
+
+ while ((nm = LIST_FIRST(list)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ pfctl_flush_my_fingerprints(&nm->nm_sublist);
+ free(nm);
+ }
+ fingerprint_count = 0;
+ class_count = 0;
+}
+
+/* Fetch the active fingerprints from the kernel */
+int
+pfctl_load_fingerprints(int dev, int opts)
+{
+ struct pf_osfp_ioctl io;
+ int i;
+
+ pfctl_flush_my_fingerprints(&classes);
+
+ for (i = 0; i >= 0; i++) {
+ memset(&io, 0, sizeof(io));
+ io.fp_getnum = i;
+ if (ioctl(dev, DIOCOSFPGET, &io)) {
+ if (errno == EBUSY)
+ break;
+ warn("DIOCOSFPGET");
+ return (1);
+ }
+ import_fingerprint(&io);
+ }
+ return (0);
+}
+
+/* List the fingerprints */
+void
+pfctl_show_fingerprints(int opts)
+{
+ if (LIST_FIRST(&classes) != NULL) {
+ if (opts & PF_OPT_SHOWALL) {
+ pfctl_print_title("OS FINGERPRINTS:");
+ printf("%u fingerprints loaded\n", fingerprint_count);
+ } else {
+ printf("Class\tVersion\tSubtype(subversion)\n");
+ printf("-----\t-------\t-------------------\n");
+ sort_name_list(opts, &classes);
+ print_name_list(opts, &classes, "");
+ }
+ }
+}
+
+/* Lookup a fingerprint */
+pf_osfp_t
+pfctl_get_fingerprint(const char *name)
+{
+ struct name_entry *nm, *class_nm, *version_nm, *subtype_nm;
+ pf_osfp_t ret = PF_OSFP_NOMATCH;
+ int class, version, subtype;
+ int unp_class, unp_version, unp_subtype;
+ int wr_len, version_len, subtype_len;
+ char *ptr, *wr_name;
+
+ if (strcasecmp(name, "unknown") == 0)
+ return (PF_OSFP_UNKNOWN);
+
+ /* Try most likely no version and no subtype */
+ if ((nm = lookup_name_list(&classes, name))) {
+ class = nm->nm_num;
+ version = PF_OSFP_ANY;
+ subtype = PF_OSFP_ANY;
+ goto found;
+ } else {
+
+ /* Chop it up into class/version/subtype */
+
+ if ((wr_name = strdup(name)) == NULL)
+ err(1, "malloc");
+ if ((ptr = strchr(wr_name, ' ')) == NULL) {
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+ *ptr++ = '\0';
+
+ /* The class is easy to find since it is delimited by a space */
+ if ((class_nm = lookup_name_list(&classes, wr_name)) == NULL) {
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+ class = class_nm->nm_num;
+
+ /* Try no subtype */
+ if ((version_nm = lookup_name_list(&class_nm->nm_sublist, ptr)))
+ {
+ version = version_nm->nm_num;
+ subtype = PF_OSFP_ANY;
+ free(wr_name);
+ goto found;
+ }
+
+
+ /*
+ * There must be a version and a subtype.
+ * We'll do some fuzzy matching to pick up things like:
+ * Linux 2.2.14 (version=2.2 subtype=14)
+ * FreeBSD 4.0-STABLE (version=4.0 subtype=STABLE)
+ * Windows 2000 SP2 (version=2000 subtype=SP2)
+ */
+#define CONNECTOR(x) ((x) == '.' || (x) == ' ' || (x) == '\t' || (x) == '-')
+ wr_len = strlen(ptr);
+ LIST_FOREACH(version_nm, &class_nm->nm_sublist, nm_entry) {
+ version_len = strlen(version_nm->nm_name);
+ if (wr_len < version_len + 2 ||
+ !CONNECTOR(ptr[version_len]))
+ continue;
+ /* first part of the string must be version */
+ if (strncasecmp(ptr, version_nm->nm_name,
+ version_len))
+ continue;
+
+ LIST_FOREACH(subtype_nm, &version_nm->nm_sublist,
+ nm_entry) {
+ subtype_len = strlen(subtype_nm->nm_name);
+ if (wr_len != version_len + subtype_len + 1)
+ continue;
+
+ /* last part of the string must be subtype */
+ if (strcasecmp(&ptr[version_len+1],
+ subtype_nm->nm_name) != 0)
+ continue;
+
+ /* Found it!! */
+ version = version_nm->nm_num;
+ subtype = subtype_nm->nm_num;
+ free(wr_name);
+ goto found;
+ }
+ }
+
+ free(wr_name);
+ return (PF_OSFP_NOMATCH);
+ }
+
+found:
+ PF_OSFP_PACK(ret, class, version, subtype);
+ if (ret != PF_OSFP_NOMATCH) {
+ PF_OSFP_UNPACK(ret, unp_class, unp_version, unp_subtype);
+ if (class != unp_class) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "classes\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ if (version != unp_version) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "versions\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ if (subtype != unp_subtype) {
+ fprintf(stderr, "warning: fingerprint table overflowed "
+ "subtypes\n");
+ return (PF_OSFP_NOMATCH);
+ }
+ }
+ if (ret == PF_OSFP_ANY) {
+ /* should never happen */
+ fprintf(stderr, "warning: fingerprint packed to 'any'\n");
+ return (PF_OSFP_NOMATCH);
+ }
+
+ return (ret);
+}
+
+/* Lookup a fingerprint name by ID */
+char *
+pfctl_lookup_fingerprint(pf_osfp_t fp, char *buf, size_t len)
+{
+ int class, version, subtype;
+ struct name_list *list;
+ struct name_entry *nm;
+
+ char *class_name, *version_name, *subtype_name;
+ class_name = version_name = subtype_name = NULL;
+
+ if (fp == PF_OSFP_UNKNOWN) {
+ strlcpy(buf, "unknown", len);
+ return (buf);
+ }
+ if (fp == PF_OSFP_ANY) {
+ strlcpy(buf, "any", len);
+ return (buf);
+ }
+
+ PF_OSFP_UNPACK(fp, class, version, subtype);
+ if (class >= (1 << _FP_CLASS_BITS) ||
+ version >= (1 << _FP_VERSION_BITS) ||
+ subtype >= (1 << _FP_SUBTYPE_BITS)) {
+ warnx("PF_OSFP_UNPACK(0x%x) failed!!", fp);
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+
+ LIST_FOREACH(nm, &classes, nm_entry) {
+ if (nm->nm_num == class) {
+ class_name = nm->nm_name;
+ if (version == PF_OSFP_ANY)
+ goto found;
+ list = &nm->nm_sublist;
+ LIST_FOREACH(nm, list, nm_entry) {
+ if (nm->nm_num == version) {
+ version_name = nm->nm_name;
+ if (subtype == PF_OSFP_ANY)
+ goto found;
+ list = &nm->nm_sublist;
+ LIST_FOREACH(nm, list, nm_entry) {
+ if (nm->nm_num == subtype) {
+ subtype_name =
+ nm->nm_name;
+ goto found;
+ }
+ } /* foreach subtype */
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+ } /* foreach version */
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+ }
+ } /* foreach class */
+
+ strlcpy(buf, "nomatch", len);
+ return (buf);
+
+found:
+ snprintf(buf, len, "%s", class_name);
+ if (version_name) {
+ strlcat(buf, " ", len);
+ strlcat(buf, version_name, len);
+ if (subtype_name) {
+ if (strchr(version_name, ' '))
+ strlcat(buf, " ", len);
+ else if (strchr(version_name, '.') &&
+ isdigit(*subtype_name))
+ strlcat(buf, ".", len);
+ else
+ strlcat(buf, " ", len);
+ strlcat(buf, subtype_name, len);
+ }
+ }
+ return (buf);
+}
+
+/* lookup a name in a list */
+struct name_entry *
+lookup_name_list(struct name_list *list, const char *name)
+{
+ struct name_entry *nm;
+ LIST_FOREACH(nm, list, nm_entry)
+ if (strcasecmp(name, nm->nm_name) == 0)
+ return (nm);
+
+ return (NULL);
+}
+
+
+void
+add_fingerprint(int dev, int opts, struct pf_osfp_ioctl *fp)
+{
+ struct pf_osfp_ioctl fptmp;
+ struct name_entry *nm_class, *nm_version, *nm_subtype;
+ int class, version, subtype;
+
+/* We expand #-# or #.#-#.# version/subtypes into multiple fingerprints */
+#define EXPAND(field) do { \
+ int _dot = -1, _start = -1, _end = -1, _i = 0; \
+ /* pick major version out of #.# */ \
+ if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.') { \
+ _dot = fp->field[_i] - '0'; \
+ _i += 2; \
+ } \
+ if (isdigit(fp->field[_i])) \
+ _start = fp->field[_i++] - '0'; \
+ else \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _start = (_start * 10) + fp->field[_i++] - '0'; \
+ if (fp->field[_i++] != '-') \
+ break; \
+ if (isdigit(fp->field[_i]) && fp->field[_i+1] == '.' && \
+ fp->field[_i] - '0' == _dot) \
+ _i += 2; \
+ else if (_dot != -1) \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _end = fp->field[_i++] - '0'; \
+ else \
+ break; \
+ if (isdigit(fp->field[_i])) \
+ _end = (_end * 10) + fp->field[_i++] - '0'; \
+ if (isdigit(fp->field[_i])) \
+ _end = (_end * 10) + fp->field[_i++] - '0'; \
+ if (fp->field[_i] != '\0') \
+ break; \
+ memcpy(&fptmp, fp, sizeof(fptmp)); \
+ for (;_start <= _end; _start++) { \
+ memset(fptmp.field, 0, sizeof(fptmp.field)); \
+ fptmp.fp_os.fp_enflags |= PF_OSFP_EXPANDED; \
+ if (_dot == -1) \
+ snprintf(fptmp.field, sizeof(fptmp.field), \
+ "%d", _start); \
+ else \
+ snprintf(fptmp.field, sizeof(fptmp.field), \
+ "%d.%d", _dot, _start); \
+ add_fingerprint(dev, opts, &fptmp); \
+ } \
+} while(0)
+
+ /* We allow "#-#" as a version or subtype and we'll expand it */
+ EXPAND(fp_os.fp_version_nm);
+ EXPAND(fp_os.fp_subtype_nm);
+
+ if (strcasecmp(fp->fp_os.fp_class_nm, "nomatch") == 0)
+ errx(1, "fingerprint class \"nomatch\" is reserved");
+
+ version = PF_OSFP_ANY;
+ subtype = PF_OSFP_ANY;
+
+ nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm);
+ if (nm_class->nm_num == 0)
+ nm_class->nm_num = ++class_count;
+ class = nm_class->nm_num;
+
+ nm_version = fingerprint_name_entry(&nm_class->nm_sublist,
+ fp->fp_os.fp_version_nm);
+ if (nm_version) {
+ if (nm_version->nm_num == 0)
+ nm_version->nm_num = ++nm_class->nm_sublist_num;
+ version = nm_version->nm_num;
+ nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist,
+ fp->fp_os.fp_subtype_nm);
+ if (nm_subtype) {
+ if (nm_subtype->nm_num == 0)
+ nm_subtype->nm_num =
+ ++nm_version->nm_sublist_num;
+ subtype = nm_subtype->nm_num;
+ }
+ }
+
+
+ DEBUG(fp, "\tsignature %d:%d:%d %s", class, version, subtype,
+ print_ioctl(fp));
+
+ PF_OSFP_PACK(fp->fp_os.fp_os, class, version, subtype);
+ fingerprint_count++;
+
+#ifdef FAKE_PF_KERNEL
+ /* Linked to the sys/net/pf_osfp.c. Call pf_osfp_add() */
+ if ((errno = pf_osfp_add(fp)))
+#else
+ if ((opts & PF_OPT_NOACTION) == 0 && ioctl(dev, DIOCOSFPADD, fp))
+#endif /* FAKE_PF_KERNEL */
+ {
+ if (errno == EEXIST) {
+ warn("Duplicate signature for %s %s %s",
+ fp->fp_os.fp_class_nm,
+ fp->fp_os.fp_version_nm,
+ fp->fp_os.fp_subtype_nm);
+
+ } else {
+ err(1, "DIOCOSFPADD");
+ }
+ }
+}
+
+/* import a fingerprint from the kernel */
+void
+import_fingerprint(struct pf_osfp_ioctl *fp)
+{
+ struct name_entry *nm_class, *nm_version, *nm_subtype;
+ int class, version, subtype;
+
+ PF_OSFP_UNPACK(fp->fp_os.fp_os, class, version, subtype);
+
+ nm_class = fingerprint_name_entry(&classes, fp->fp_os.fp_class_nm);
+ if (nm_class->nm_num == 0) {
+ nm_class->nm_num = class;
+ class_count = MAX(class_count, class);
+ }
+
+ nm_version = fingerprint_name_entry(&nm_class->nm_sublist,
+ fp->fp_os.fp_version_nm);
+ if (nm_version) {
+ if (nm_version->nm_num == 0) {
+ nm_version->nm_num = version;
+ nm_class->nm_sublist_num = MAX(nm_class->nm_sublist_num,
+ version);
+ }
+ nm_subtype = fingerprint_name_entry(&nm_version->nm_sublist,
+ fp->fp_os.fp_subtype_nm);
+ if (nm_subtype) {
+ if (nm_subtype->nm_num == 0) {
+ nm_subtype->nm_num = subtype;
+ nm_version->nm_sublist_num =
+ MAX(nm_version->nm_sublist_num, subtype);
+ }
+ }
+ }
+
+
+ fingerprint_count++;
+ DEBUG(fp, "import signature %d:%d:%d", class, version, subtype);
+}
+
+/* Find an entry for a fingerprints class/version/subtype */
+struct name_entry *
+fingerprint_name_entry(struct name_list *list, char *name)
+{
+ struct name_entry *nm_entry;
+
+ if (name == NULL || strlen(name) == 0)
+ return (NULL);
+
+ LIST_FOREACH(nm_entry, list, nm_entry) {
+ if (strcasecmp(nm_entry->nm_name, name) == 0) {
+ /* We'll move this to the front of the list later */
+ LIST_REMOVE(nm_entry, nm_entry);
+ break;
+ }
+ }
+ if (nm_entry == NULL) {
+ nm_entry = calloc(1, sizeof(*nm_entry));
+ if (nm_entry == NULL)
+ err(1, "calloc");
+ LIST_INIT(&nm_entry->nm_sublist);
+ strlcpy(nm_entry->nm_name, name, sizeof(nm_entry->nm_name));
+ }
+ LIST_INSERT_HEAD(list, nm_entry, nm_entry);
+ return (nm_entry);
+}
+
+
+void
+print_name_list(int opts, struct name_list *nml, const char *prefix)
+{
+ char newprefix[32];
+ struct name_entry *nm;
+
+ LIST_FOREACH(nm, nml, nm_entry) {
+ snprintf(newprefix, sizeof(newprefix), "%s%s\t", prefix,
+ nm->nm_name);
+ printf("%s\n", newprefix);
+ print_name_list(opts, &nm->nm_sublist, newprefix);
+ }
+}
+
+void
+sort_name_list(int opts, struct name_list *nml)
+{
+ struct name_list new;
+ struct name_entry *nm, *nmsearch, *nmlast;
+
+ /* yes yes, it's a very slow sort. so sue me */
+
+ LIST_INIT(&new);
+
+ while ((nm = LIST_FIRST(nml)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ nmlast = NULL;
+ LIST_FOREACH(nmsearch, &new, nm_entry) {
+ if (strcasecmp(nmsearch->nm_name, nm->nm_name) > 0) {
+ LIST_INSERT_BEFORE(nmsearch, nm, nm_entry);
+ break;
+ }
+ nmlast = nmsearch;
+ }
+ if (nmsearch == NULL) {
+ if (nmlast)
+ LIST_INSERT_AFTER(nmlast, nm, nm_entry);
+ else
+ LIST_INSERT_HEAD(&new, nm, nm_entry);
+ }
+
+ sort_name_list(opts, &nm->nm_sublist);
+ }
+ nmlast = NULL;
+ while ((nm = LIST_FIRST(&new)) != NULL) {
+ LIST_REMOVE(nm, nm_entry);
+ if (nmlast == NULL)
+ LIST_INSERT_HEAD(nml, nm, nm_entry);
+ else
+ LIST_INSERT_AFTER(nmlast, nm, nm_entry);
+ nmlast = nm;
+ }
+}
+
+/* parse the next integer in a formatted config file line */
+int
+get_int(char **line, size_t *len, int *var, int *mod,
+ const char *name, int flags, int max, const char *filename, int lineno)
+{
+ int fieldlen, i;
+ char *field;
+ long val = 0;
+
+ if (mod)
+ *mod = 0;
+ *var = 0;
+
+ field = get_field(line, len, &fieldlen);
+ if (field == NULL)
+ return (1);
+ if (fieldlen == 0) {
+ fprintf(stderr, "%s:%d empty %s\n", filename, lineno, name);
+ return (1);
+ }
+
+ i = 0;
+ if ((*field == '%' || *field == 'S' || *field == 'T' || *field == '*')
+ && fieldlen >= 1) {
+ switch (*field) {
+ case 'S':
+ if (mod && (flags & T_MSS))
+ *mod = T_MSS;
+ if (fieldlen == 1)
+ return (0);
+ break;
+ case 'T':
+ if (mod && (flags & T_MTU))
+ *mod = T_MTU;
+ if (fieldlen == 1)
+ return (0);
+ break;
+ case '*':
+ if (fieldlen != 1) {
+ fprintf(stderr, "%s:%d long '%c' %s\n",
+ filename, lineno, *field, name);
+ return (1);
+ }
+ if (mod && (flags & T_DC)) {
+ *mod = T_DC;
+ return (0);
+ }
+ case '%':
+ if (mod && (flags & T_MOD))
+ *mod = T_MOD;
+ if (fieldlen == 1) {
+ fprintf(stderr, "%s:%d modulus %s must have a "
+ "value\n", filename, lineno, name);
+ return (1);
+ }
+ break;
+ }
+ if (mod == NULL || *mod == 0) {
+ fprintf(stderr, "%s:%d does not allow %c' %s\n",
+ filename, lineno, *field, name);
+ return (1);
+ }
+ i++;
+ }
+
+ for (; i < fieldlen; i++) {
+ if (field[i] < '0' || field[i] > '9') {
+ fprintf(stderr, "%s:%d non-digit character in %s\n",
+ filename, lineno, name);
+ return (1);
+ }
+ val = val * 10 + field[i] - '0';
+ if (val < 0) {
+ fprintf(stderr, "%s:%d %s overflowed\n", filename,
+ lineno, name);
+ return (1);
+ }
+ }
+
+ if (val > max) {
+ fprintf(stderr, "%s:%d %s value %ld > %d\n", filename, lineno,
+ name, val, max);
+ return (1);
+ }
+ *var = (int)val;
+
+ return (0);
+}
+
+/* parse the next string in a formatted config file line */
+int
+get_str(char **line, size_t *len, char **v, const char *name, int minlen,
+ const char *filename, int lineno)
+{
+ int fieldlen;
+ char *ptr;
+
+ ptr = get_field(line, len, &fieldlen);
+ if (ptr == NULL)
+ return (1);
+ if (fieldlen < minlen) {
+ fprintf(stderr, "%s:%d too short %s\n", filename, lineno, name);
+ return (1);
+ }
+ if ((*v = malloc(fieldlen + 1)) == NULL) {
+ perror("malloc()");
+ return (1);
+ }
+ memcpy(*v, ptr, fieldlen);
+ (*v)[fieldlen] = '\0';
+
+ return (0);
+}
+
+/* Parse out the TCP opts */
+int
+get_tcpopts(const char *filename, int lineno, const char *tcpopts,
+ pf_tcpopts_t *packed, int *optcnt, int *mss, int *mss_mod, int *wscale,
+ int *wscale_mod, int *ts0)
+{
+ int i, opt;
+
+ *packed = 0;
+ *optcnt = 0;
+ *wscale = 0;
+ *wscale_mod = T_DC;
+ *mss = 0;
+ *mss_mod = T_DC;
+ *ts0 = 0;
+ if (strcmp(tcpopts, ".") == 0)
+ return (0);
+
+ for (i = 0; tcpopts[i] && *optcnt < PF_OSFP_MAX_OPTS;) {
+ switch ((opt = toupper(tcpopts[i++]))) {
+ case 'N': /* FALLTHROUGH */
+ case 'S':
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ (opt == 'N' ? PF_OSFP_TCPOPT_NOP :
+ PF_OSFP_TCPOPT_SACK);
+ break;
+ case 'W': /* FALLTHROUGH */
+ case 'M': {
+ int *this_mod, *this;
+
+ if (opt == 'W') {
+ this = wscale;
+ this_mod = wscale_mod;
+ } else {
+ this = mss;
+ this_mod = mss_mod;
+ }
+ *this = 0;
+ *this_mod = 0;
+
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ (opt == 'W' ? PF_OSFP_TCPOPT_WSCALE :
+ PF_OSFP_TCPOPT_MSS);
+ if (tcpopts[i] == '*' && (tcpopts[i + 1] == '\0' ||
+ tcpopts[i + 1] == ',')) {
+ *this_mod = T_DC;
+ i++;
+ break;
+ }
+
+ if (tcpopts[i] == '%') {
+ *this_mod = T_MOD;
+ i++;
+ }
+ do {
+ if (!isdigit(tcpopts[i])) {
+ fprintf(stderr, "%s:%d unknown "
+ "character '%c' in %c TCP opt\n",
+ filename, lineno, tcpopts[i], opt);
+ return (1);
+ }
+ *this = (*this * 10) + tcpopts[i++] - '0';
+ } while(tcpopts[i] != ',' && tcpopts[i] != '\0');
+ break;
+ }
+ case 'T':
+ if (tcpopts[i] == '0') {
+ *ts0 = 1;
+ i++;
+ }
+ *packed = (*packed << PF_OSFP_TCPOPT_BITS) |
+ PF_OSFP_TCPOPT_TS;
+ break;
+ }
+ (*optcnt) ++;
+ if (tcpopts[i] == '\0')
+ break;
+ if (tcpopts[i] != ',') {
+ fprintf(stderr, "%s:%d unknown option to %c TCP opt\n",
+ filename, lineno, opt);
+ return (1);
+ }
+ i++;
+ }
+
+ return (0);
+}
+
+/* rip the next field ouf of a formatted config file line */
+char *
+get_field(char **line, size_t *len, int *fieldlen)
+{
+ char *ret, *ptr = *line;
+ size_t plen = *len;
+
+
+ while (plen && isspace(*ptr)) {
+ plen--;
+ ptr++;
+ }
+ ret = ptr;
+ *fieldlen = 0;
+
+ for (; plen > 0 && *ptr != ':'; plen--, ptr++)
+ (*fieldlen)++;
+ if (plen) {
+ *line = ptr + 1;
+ *len = plen - 1;
+ } else {
+ *len = 0;
+ }
+ while (*fieldlen && isspace(ret[*fieldlen - 1]))
+ (*fieldlen)--;
+ return (ret);
+}
+
+
+const char *
+print_ioctl(struct pf_osfp_ioctl *fp)
+{
+ static char buf[1024];
+ char tmp[32];
+ int i, opt;
+
+ *buf = '\0';
+ if (fp->fp_flags & PF_OSFP_WSIZE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else if (fp->fp_flags & PF_OSFP_WSIZE_MSS)
+ strlcat(buf, "S", sizeof(buf));
+ else if (fp->fp_flags & PF_OSFP_WSIZE_MTU)
+ strlcat(buf, "T", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_WSIZE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_wsize);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_ttl);
+ strlcat(buf, tmp, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_flags & PF_OSFP_DF)
+ strlcat(buf, "1", sizeof(buf));
+ else
+ strlcat(buf, "0", sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_flags & PF_OSFP_PSIZE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_PSIZE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_psize);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ if (fp->fp_optcnt == 0)
+ strlcat(buf, ".", sizeof(buf));
+ for (i = fp->fp_optcnt - 1; i >= 0; i--) {
+ opt = fp->fp_tcpopts >> (i * PF_OSFP_TCPOPT_BITS);
+ opt &= (1 << PF_OSFP_TCPOPT_BITS) - 1;
+ switch (opt) {
+ case PF_OSFP_TCPOPT_NOP:
+ strlcat(buf, "N", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_SACK:
+ strlcat(buf, "S", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_TS:
+ strlcat(buf, "T", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_TS0)
+ strlcat(buf, "0", sizeof(buf));
+ break;
+ case PF_OSFP_TCPOPT_MSS:
+ strlcat(buf, "M", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_MSS_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_MSS_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_mss);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ break;
+ case PF_OSFP_TCPOPT_WSCALE:
+ strlcat(buf, "W", sizeof(buf));
+ if (fp->fp_flags & PF_OSFP_WSCALE_DC)
+ strlcat(buf, "*", sizeof(buf));
+ else {
+ if (fp->fp_flags & PF_OSFP_WSCALE_MOD)
+ strlcat(buf, "%", sizeof(buf));
+ snprintf(tmp, sizeof(tmp), "%d", fp->fp_wscale);
+ strlcat(buf, tmp, sizeof(buf));
+ }
+ break;
+ }
+
+ if (i != 0)
+ strlcat(buf, ",", sizeof(buf));
+ }
+ strlcat(buf, ":", sizeof(buf));
+
+ strlcat(buf, fp->fp_os.fp_class_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+ strlcat(buf, fp->fp_os.fp_version_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+ strlcat(buf, fp->fp_os.fp_subtype_nm, sizeof(buf));
+ strlcat(buf, ":", sizeof(buf));
+
+ snprintf(tmp, sizeof(tmp), "TcpOpts %d 0x%llx", fp->fp_optcnt,
+ (long long int)fp->fp_tcpopts);
+ strlcat(buf, tmp, sizeof(buf));
+
+ return (buf);
+}
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
new file mode 100644
index 000000000000..b8531067d3f6
--- /dev/null
+++ b/sbin/pfctl/pfctl_parser.c
@@ -0,0 +1,2101 @@
+/* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * Copyright (c) 2002,2003 Henning Brauer
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <sys/proc.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/icmp6.h>
+#include <netinet/tcp.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <assert.h>
+#include <search.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <err.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <unistd.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+void print_op (u_int8_t, const char *, const char *);
+void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int);
+void print_ugid (u_int8_t, id_t, id_t, const char *);
+void print_flags (uint16_t);
+void print_fromto(struct pf_rule_addr *, pf_osfp_t,
+ struct pf_rule_addr *, sa_family_t, u_int8_t, int, int);
+int ifa_skip_if(const char *filter, struct node_host *p);
+
+struct node_host *host_if(const char *, int);
+struct node_host *host_ip(const char *, int);
+struct node_host *host_dns(const char *, int, int);
+
+const char * const tcpflags = "FSRPAUEWe";
+
+static const struct icmptypeent icmp_type[] = {
+ { "echoreq", ICMP_ECHO },
+ { "echorep", ICMP_ECHOREPLY },
+ { "unreach", ICMP_UNREACH },
+ { "squench", ICMP_SOURCEQUENCH },
+ { "redir", ICMP_REDIRECT },
+ { "althost", ICMP_ALTHOSTADDR },
+ { "routeradv", ICMP_ROUTERADVERT },
+ { "routersol", ICMP_ROUTERSOLICIT },
+ { "timex", ICMP_TIMXCEED },
+ { "paramprob", ICMP_PARAMPROB },
+ { "timereq", ICMP_TSTAMP },
+ { "timerep", ICMP_TSTAMPREPLY },
+ { "inforeq", ICMP_IREQ },
+ { "inforep", ICMP_IREQREPLY },
+ { "maskreq", ICMP_MASKREQ },
+ { "maskrep", ICMP_MASKREPLY },
+ { "trace", ICMP_TRACEROUTE },
+ { "dataconv", ICMP_DATACONVERR },
+ { "mobredir", ICMP_MOBILE_REDIRECT },
+ { "ipv6-where", ICMP_IPV6_WHEREAREYOU },
+ { "ipv6-here", ICMP_IPV6_IAMHERE },
+ { "mobregreq", ICMP_MOBILE_REGREQUEST },
+ { "mobregrep", ICMP_MOBILE_REGREPLY },
+ { "skip", ICMP_SKIP },
+ { "photuris", ICMP_PHOTURIS }
+};
+
+static const struct icmptypeent icmp6_type[] = {
+ { "unreach", ICMP6_DST_UNREACH },
+ { "toobig", ICMP6_PACKET_TOO_BIG },
+ { "timex", ICMP6_TIME_EXCEEDED },
+ { "paramprob", ICMP6_PARAM_PROB },
+ { "echoreq", ICMP6_ECHO_REQUEST },
+ { "echorep", ICMP6_ECHO_REPLY },
+ { "groupqry", ICMP6_MEMBERSHIP_QUERY },
+ { "listqry", MLD_LISTENER_QUERY },
+ { "grouprep", ICMP6_MEMBERSHIP_REPORT },
+ { "listenrep", MLD_LISTENER_REPORT },
+ { "groupterm", ICMP6_MEMBERSHIP_REDUCTION },
+ { "listendone", MLD_LISTENER_DONE },
+ { "routersol", ND_ROUTER_SOLICIT },
+ { "routeradv", ND_ROUTER_ADVERT },
+ { "neighbrsol", ND_NEIGHBOR_SOLICIT },
+ { "neighbradv", ND_NEIGHBOR_ADVERT },
+ { "redir", ND_REDIRECT },
+ { "routrrenum", ICMP6_ROUTER_RENUMBERING },
+ { "wrureq", ICMP6_WRUREQUEST },
+ { "wrurep", ICMP6_WRUREPLY },
+ { "fqdnreq", ICMP6_FQDN_QUERY },
+ { "fqdnrep", ICMP6_FQDN_REPLY },
+ { "niqry", ICMP6_NI_QUERY },
+ { "nirep", ICMP6_NI_REPLY },
+ { "mtraceresp", MLD_MTRACE_RESP },
+ { "mtrace", MLD_MTRACE },
+ { "listenrepv2", MLDV2_LISTENER_REPORT },
+};
+
+static const struct icmpcodeent icmp_code[] = {
+ { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET },
+ { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST },
+ { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL },
+ { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT },
+ { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG },
+ { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL },
+ { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN },
+ { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN },
+ { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED },
+ { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB },
+ { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB },
+ { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET },
+ { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST },
+ { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB },
+ { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE },
+ { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF },
+ { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET },
+ { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST },
+ { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET },
+ { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST },
+ { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL },
+ { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON },
+ { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS },
+ { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS },
+ { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR },
+ { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT },
+ { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH },
+ { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX },
+ { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED },
+ { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED }
+};
+
+static const struct icmpcodeent icmp6_code[] = {
+ { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN },
+ { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE },
+ { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR },
+ { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE },
+ { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR },
+ { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT },
+ { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT },
+ { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY },
+ { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER },
+ { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER },
+ { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK },
+ { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER }
+};
+
+const struct pf_timeout pf_timeouts[] = {
+ { "tcp.first", PFTM_TCP_FIRST_PACKET },
+ { "tcp.opening", PFTM_TCP_OPENING },
+ { "tcp.established", PFTM_TCP_ESTABLISHED },
+ { "tcp.closing", PFTM_TCP_CLOSING },
+ { "tcp.finwait", PFTM_TCP_FIN_WAIT },
+ { "tcp.closed", PFTM_TCP_CLOSED },
+ { "tcp.tsdiff", PFTM_TS_DIFF },
+ { "sctp.first", PFTM_SCTP_FIRST_PACKET },
+ { "sctp.opening", PFTM_SCTP_OPENING },
+ { "sctp.established", PFTM_SCTP_ESTABLISHED },
+ { "sctp.closing", PFTM_SCTP_CLOSING },
+ { "sctp.closed", PFTM_SCTP_CLOSED },
+ { "udp.first", PFTM_UDP_FIRST_PACKET },
+ { "udp.single", PFTM_UDP_SINGLE },
+ { "udp.multiple", PFTM_UDP_MULTIPLE },
+ { "icmp.first", PFTM_ICMP_FIRST_PACKET },
+ { "icmp.error", PFTM_ICMP_ERROR_REPLY },
+ { "other.first", PFTM_OTHER_FIRST_PACKET },
+ { "other.single", PFTM_OTHER_SINGLE },
+ { "other.multiple", PFTM_OTHER_MULTIPLE },
+ { "frag", PFTM_FRAG },
+ { "interval", PFTM_INTERVAL },
+ { "adaptive.start", PFTM_ADAPTIVE_START },
+ { "adaptive.end", PFTM_ADAPTIVE_END },
+ { "src.track", PFTM_SRC_NODE },
+ { NULL, 0 }
+};
+
+static struct hsearch_data isgroup_map;
+
+static __attribute__((constructor)) void
+pfctl_parser_init(void)
+{
+ /*
+ * As hdestroy() will never be called on these tables, it will be
+ * safe to use references into the stored data as keys.
+ */
+ if (hcreate_r(0, &isgroup_map) == 0)
+ err(1, "Failed to create interface group query response map");
+}
+
+void
+copy_satopfaddr(struct pf_addr *pfa, struct sockaddr *sa)
+{
+ if (sa->sa_family == AF_INET6)
+ pfa->v6 = ((struct sockaddr_in6 *)sa)->sin6_addr;
+ else if (sa->sa_family == AF_INET)
+ pfa->v4 = ((struct sockaddr_in *)sa)->sin_addr;
+ else
+ warnx("unhandled af %d", sa->sa_family);
+}
+
+const struct icmptypeent *
+geticmptypebynumber(u_int8_t type, sa_family_t af)
+{
+ size_t i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < nitems(icmp_type); i++) {
+ if (type == icmp_type[i].type)
+ return (&icmp_type[i]);
+ }
+ } else {
+ for (i=0; i < nitems(icmp6_type); i++) {
+ if (type == icmp6_type[i].type)
+ return (&icmp6_type[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmptypeent *
+geticmptypebyname(char *w, sa_family_t af)
+{
+ size_t i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < nitems(icmp_type); i++) {
+ if (!strcmp(w, icmp_type[i].name))
+ return (&icmp_type[i]);
+ }
+ } else {
+ for (i=0; i < nitems(icmp6_type); i++) {
+ if (!strcmp(w, icmp6_type[i].name))
+ return (&icmp6_type[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmpcodeent *
+geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af)
+{
+ size_t i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < nitems(icmp_code); i++) {
+ if (type == icmp_code[i].type &&
+ code == icmp_code[i].code)
+ return (&icmp_code[i]);
+ }
+ } else {
+ for (i=0; i < nitems(icmp6_code); i++) {
+ if (type == icmp6_code[i].type &&
+ code == icmp6_code[i].code)
+ return (&icmp6_code[i]);
+ }
+ }
+ return (NULL);
+}
+
+const struct icmpcodeent *
+geticmpcodebyname(u_long type, char *w, sa_family_t af)
+{
+ size_t i;
+
+ if (af != AF_INET6) {
+ for (i=0; i < nitems(icmp_code); i++) {
+ if (type == icmp_code[i].type &&
+ !strcmp(w, icmp_code[i].name))
+ return (&icmp_code[i]);
+ }
+ } else {
+ for (i=0; i < nitems(icmp6_code); i++) {
+ if (type == icmp6_code[i].type &&
+ !strcmp(w, icmp6_code[i].name))
+ return (&icmp6_code[i]);
+ }
+ }
+ return (NULL);
+}
+
+void
+print_op(u_int8_t op, const char *a1, const char *a2)
+{
+ if (op == PF_OP_IRG)
+ printf(" %s >< %s", a1, a2);
+ else if (op == PF_OP_XRG)
+ printf(" %s <> %s", a1, a2);
+ else if (op == PF_OP_EQ)
+ printf(" = %s", a1);
+ else if (op == PF_OP_NE)
+ printf(" != %s", a1);
+ else if (op == PF_OP_LT)
+ printf(" < %s", a1);
+ else if (op == PF_OP_LE)
+ printf(" <= %s", a1);
+ else if (op == PF_OP_GT)
+ printf(" > %s", a1);
+ else if (op == PF_OP_GE)
+ printf(" >= %s", a1);
+ else if (op == PF_OP_RRG)
+ printf(" %s:%s", a1, a2);
+}
+
+void
+print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int numeric)
+{
+ char a1[6], a2[6];
+ struct servent *s;
+
+ if (!numeric)
+ s = getservbyport(p1, proto);
+ else
+ s = NULL;
+ p1 = ntohs(p1);
+ p2 = ntohs(p2);
+ snprintf(a1, sizeof(a1), "%u", p1);
+ snprintf(a2, sizeof(a2), "%u", p2);
+ printf(" port");
+ if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE))
+ print_op(op, s->s_name, a2);
+ else
+ print_op(op, a1, a2);
+}
+
+void
+print_ugid(u_int8_t op, id_t i1, id_t i2, const char *t)
+{
+ char a1[11], a2[11];
+
+ snprintf(a1, sizeof(a1), "%ju", (uintmax_t)i1);
+ snprintf(a2, sizeof(a2), "%ju", (uintmax_t)i2);
+ printf(" %s", t);
+ if (i1 == -1 && (op == PF_OP_EQ || op == PF_OP_NE))
+ print_op(op, "unknown", a2);
+ else
+ print_op(op, a1, a2);
+}
+
+void
+print_flags(uint16_t f)
+{
+ int i;
+
+ for (i = 0; tcpflags[i]; ++i)
+ if (f & (1 << i))
+ printf("%c", tcpflags[i]);
+}
+
+void
+print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst,
+ sa_family_t af, u_int8_t proto, int opts, int numeric)
+{
+ char buf[PF_OSFP_LEN*3];
+ int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG);
+
+ if (src->addr.type == PF_ADDR_ADDRMASK &&
+ dst->addr.type == PF_ADDR_ADDRMASK &&
+ PF_AZERO(&src->addr.v.a.addr, AF_INET6) &&
+ PF_AZERO(&src->addr.v.a.mask, AF_INET6) &&
+ PF_AZERO(&dst->addr.v.a.addr, AF_INET6) &&
+ PF_AZERO(&dst->addr.v.a.mask, AF_INET6) &&
+ !src->neg && !dst->neg &&
+ !src->port_op && !dst->port_op &&
+ osfp == PF_OSFP_ANY)
+ printf(" all");
+ else {
+ printf(" from ");
+ if (src->neg)
+ printf("! ");
+ print_addr(&src->addr, af, verbose);
+ if (src->port_op)
+ print_port(src->port_op, src->port[0],
+ src->port[1],
+ proto == IPPROTO_TCP ? "tcp" : "udp",
+ numeric);
+ if (osfp != PF_OSFP_ANY)
+ printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf,
+ sizeof(buf)));
+
+ printf(" to ");
+ if (dst->neg)
+ printf("! ");
+ print_addr(&dst->addr, af, verbose);
+ if (dst->port_op)
+ print_port(dst->port_op, dst->port[0],
+ dst->port[1],
+ proto == IPPROTO_TCP ? "tcp" : "udp",
+ numeric);
+ }
+}
+
+void
+print_pool(struct pfctl_pool *pool, u_int16_t p1, u_int16_t p2, int id)
+{
+ struct pfctl_pooladdr *pooladdr;
+
+ if ((TAILQ_FIRST(&pool->list) != NULL) &&
+ TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL)
+ printf("{ ");
+ TAILQ_FOREACH(pooladdr, &pool->list, entries){
+ switch (id) {
+ case PF_NAT:
+ case PF_RDR:
+ case PF_BINAT:
+ print_addr(&pooladdr->addr, pooladdr->af, 0);
+ break;
+ case PF_PASS:
+ case PF_MATCH:
+ if (PF_AZERO(&pooladdr->addr.v.a.addr, pooladdr->af))
+ printf("%s", pooladdr->ifname);
+ else {
+ printf("(%s ", pooladdr->ifname);
+ print_addr(&pooladdr->addr, pooladdr->af, 0);
+ printf(")");
+ }
+ break;
+ default:
+ break;
+ }
+ if (TAILQ_NEXT(pooladdr, entries) != NULL)
+ printf(", ");
+ else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL)
+ printf(" }");
+ }
+ switch (id) {
+ case PF_NAT:
+ if ((p1 != PF_NAT_PROXY_PORT_LOW ||
+ p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) {
+ if (p1 == p2)
+ printf(" port %u", p1);
+ else
+ printf(" port %u:%u", p1, p2);
+ }
+ break;
+ case PF_RDR:
+ if (p1) {
+ printf(" port %u", p1);
+ if (p2 && (p2 != p1))
+ printf(":%u", p2);
+ }
+ break;
+ default:
+ break;
+ }
+ switch (pool->opts & PF_POOL_TYPEMASK) {
+ case PF_POOL_NONE:
+ break;
+ case PF_POOL_BITMASK:
+ printf(" bitmask");
+ break;
+ case PF_POOL_RANDOM:
+ printf(" random");
+ break;
+ case PF_POOL_SRCHASH:
+ printf(" source-hash 0x%08x%08x%08x%08x",
+ pool->key.key32[0], pool->key.key32[1],
+ pool->key.key32[2], pool->key.key32[3]);
+ break;
+ case PF_POOL_ROUNDROBIN:
+ printf(" round-robin");
+ break;
+ }
+ if (pool->opts & PF_POOL_STICKYADDR)
+ printf(" sticky-address");
+ if (pool->opts & PF_POOL_ENDPI)
+ printf(" endpoint-independent");
+ if (id == PF_NAT && p1 == 0 && p2 == 0)
+ printf(" static-port");
+ if (pool->mape.offset > 0)
+ printf(" map-e-portset %u/%u/%u",
+ pool->mape.offset, pool->mape.psidlen, pool->mape.psid);
+ if (pool->opts & PF_POOL_IPV6NH)
+ printf(" prefer-ipv6-nexthop");
+}
+
+void
+print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts)
+{
+ struct pfctl_status_counter *c;
+ char statline[80], *running;
+ time_t runtime;
+ int i;
+ char buf[PF_MD5_DIGEST_LENGTH * 2 + 1];
+ static const char hex[] = "0123456789abcdef";
+
+ runtime = time(NULL) - s->since;
+ running = s->running ? "Enabled" : "Disabled";
+
+ if (s->since) {
+ unsigned int sec, min, hrs;
+ time_t day = runtime;
+
+ sec = day % 60;
+ day /= 60;
+ min = day % 60;
+ day /= 60;
+ hrs = day % 24;
+ day /= 24;
+ snprintf(statline, sizeof(statline),
+ "Status: %s for %lld days %.2u:%.2u:%.2u",
+ running, (long long)day, hrs, min, sec);
+ } else
+ snprintf(statline, sizeof(statline), "Status: %s", running);
+ printf("%-44s", statline);
+ switch (s->debug) {
+ case PF_DEBUG_NONE:
+ printf("%15s\n\n", "Debug: None");
+ break;
+ case PF_DEBUG_URGENT:
+ printf("%15s\n\n", "Debug: Urgent");
+ break;
+ case PF_DEBUG_MISC:
+ printf("%15s\n\n", "Debug: Misc");
+ break;
+ case PF_DEBUG_NOISY:
+ printf("%15s\n\n", "Debug: Loud");
+ break;
+ }
+
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Hostid: 0x%08x\n", s->hostid);
+
+ for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) {
+ buf[i + i] = hex[s->pf_chksum[i] >> 4];
+ buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f];
+ }
+ buf[i + i] = '\0';
+ printf("Checksum: 0x%s\n\n", buf);
+ }
+
+ if (s->ifname[0] != 0) {
+ printf("Interface Stats for %-16s %5s %16s\n",
+ s->ifname, "IPv4", "IPv6");
+ printf(" %-25s %14llu %16llu\n", "Bytes In",
+ (unsigned long long)s->bcounters[0][0],
+ (unsigned long long)s->bcounters[1][0]);
+ printf(" %-25s %14llu %16llu\n", "Bytes Out",
+ (unsigned long long)s->bcounters[0][1],
+ (unsigned long long)s->bcounters[1][1]);
+ printf(" Packets In\n");
+ printf(" %-23s %14llu %16llu\n", "Passed",
+ (unsigned long long)s->pcounters[0][0][PF_PASS],
+ (unsigned long long)s->pcounters[1][0][PF_PASS]);
+ printf(" %-23s %14llu %16llu\n", "Blocked",
+ (unsigned long long)s->pcounters[0][0][PF_DROP],
+ (unsigned long long)s->pcounters[1][0][PF_DROP]);
+ printf(" Packets Out\n");
+ printf(" %-23s %14llu %16llu\n", "Passed",
+ (unsigned long long)s->pcounters[0][1][PF_PASS],
+ (unsigned long long)s->pcounters[1][1][PF_PASS]);
+ printf(" %-23s %14llu %16llu\n\n", "Blocked",
+ (unsigned long long)s->pcounters[0][1][PF_DROP],
+ (unsigned long long)s->pcounters[1][1][PF_DROP]);
+ }
+ printf("%-27s %14s %16s\n", "State Table", "Total", "Rate");
+ printf(" %-25s %14ju %14s\n", "current entries", s->states, "");
+ TAILQ_FOREACH(c, &s->fcounters, entry) {
+ printf(" %-25s %14ju ", c->name, c->counter);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)c->counter / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Source Tracking Table\n");
+ printf(" %-25s %14ju %14s\n", "current entries",
+ s->src_nodes, "");
+ TAILQ_FOREACH(c, &s->scounters, entry) {
+ printf(" %-25s %14ju ", c->name, c->counter);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)c->counter / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Fragments\n");
+ printf(" %-25s %14ju %14s\n", "current entries",
+ s->fragments, "");
+ TAILQ_FOREACH(c, &s->ncounters, entry) {
+ printf(" %-25s %14ju ", c->name,
+ c->counter);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)c->counter / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ }
+ printf("Counters\n");
+ TAILQ_FOREACH(c, &s->counters, entry) {
+ printf(" %-25s %14ju ", c->name, c->counter);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)c->counter / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+ if (opts & PF_OPT_VERBOSE) {
+ printf("Limit Counters\n");
+ TAILQ_FOREACH(c, &s->lcounters, entry) {
+ printf(" %-25s %14ju ", c->name, c->counter);
+ if (runtime > 0)
+ printf("%14.1f/s\n",
+ (double)c->counter / (double)runtime);
+ else
+ printf("%14s\n", "");
+ }
+
+ printf("Syncookies\n");
+ assert(cookies->mode <= PFCTL_SYNCOOKIES_ADAPTIVE);
+ printf(" %-25s %s\n", "mode",
+ PFCTL_SYNCOOKIES_MODE_NAMES[cookies->mode]);
+ printf(" %-25s %s\n", "active",
+ s->syncookies_active ? "active" : "inactive");
+ if (opts & PF_OPT_VERBOSE2) {
+ printf(" %-25s %d %%\n", "highwater", cookies->highwater);
+ printf(" %-25s %d %%\n", "lowwater", cookies->lowwater);
+ printf(" %-25s %d\n", "halfopen states", cookies->halfopen_states);
+ }
+ printf("Reassemble %24s %s\n",
+ s->reass & PF_REASS_ENABLED ? "yes" : "no",
+ s->reass & PF_REASS_NODF ? "no-df" : ""
+ );
+ }
+}
+
+void
+print_running(struct pfctl_status *status)
+{
+ printf("%s\n", status->running ? "Enabled" : "Disabled");
+}
+
+void
+print_src_node(struct pfctl_src_node *sn, int opts)
+{
+ struct pf_addr_wrap aw;
+ uint64_t min, sec;
+ const char *sn_type_names[] = PF_SN_TYPE_NAMES;
+
+ memset(&aw, 0, sizeof(aw));
+ if (sn->af == AF_INET)
+ aw.v.a.mask.addr32[0] = 0xffffffff;
+ else
+ memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask));
+
+ aw.v.a.addr = sn->addr;
+ print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2);
+ printf(" -> ");
+ aw.v.a.addr = sn->raddr;
+ print_addr(&aw, sn->raf, opts & PF_OPT_VERBOSE2);
+ printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states,
+ sn->conn, sn->conn_rate.count / 1000,
+ (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds);
+ if (opts & PF_OPT_VERBOSE) {
+ sec = sn->creation % 60;
+ sn->creation /= 60;
+ min = sn->creation % 60;
+ sn->creation /= 60;
+ printf(" age %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64,
+ sn->creation, min, sec);
+ if (sn->states == 0) {
+ sec = sn->expire % 60;
+ sn->expire /= 60;
+ min = sn->expire % 60;
+ sn->expire /= 60;
+ printf(", expires in %.2" PRIu64 ":%.2" PRIu64 ":%.2" PRIu64,
+ sn->expire, min, sec);
+ }
+ printf(", %" PRIu64 " pkts, %" PRIu64 " bytes",
+ sn->packets[0] + sn->packets[1],
+ sn->bytes[0] + sn->bytes[1]);
+ switch (sn->ruletype) {
+ case PF_NAT:
+ if (sn->rule != -1)
+ printf(", nat rule %u", sn->rule);
+ break;
+ case PF_RDR:
+ if (sn->rule != -1)
+ printf(", rdr rule %u", sn->rule);
+ break;
+ case PF_PASS:
+ case PF_MATCH:
+ if (sn->rule != -1)
+ printf(", filter rule %u", sn->rule);
+ break;
+ }
+ printf(", %s", sn_type_names[sn->type]);
+ printf("\n");
+ }
+}
+
+static void
+print_eth_addr(const struct pfctl_eth_addr *a)
+{
+ int i, masklen = ETHER_ADDR_LEN * 8;
+ bool seen_unset = false;
+
+ for (i = 0; i < ETHER_ADDR_LEN; i++) {
+ if (a->addr[i] != 0)
+ break;
+ }
+
+ /* Unset, so don't print anything. */
+ if (i == ETHER_ADDR_LEN)
+ return;
+
+ printf("%s%02x:%02x:%02x:%02x:%02x:%02x", a->neg ? "! " : "",
+ a->addr[0], a->addr[1], a->addr[2], a->addr[3], a->addr[4],
+ a->addr[5]);
+
+ for (i = 0; i < (ETHER_ADDR_LEN * 8); i++) {
+ bool isset = a->mask[i / 8] & (1 << i % 8);
+
+ if (! seen_unset) {
+ if (isset)
+ continue;
+ seen_unset = true;
+ masklen = i;
+ } else {
+ /* Not actually a continuous mask, so print the whole
+ * thing. */
+ if (isset)
+ break;
+ continue;
+ }
+ }
+
+ if (masklen == (ETHER_ADDR_LEN * 8))
+ return;
+
+ if (i == (ETHER_ADDR_LEN * 8)) {
+ printf("/%d", masklen);
+ return;
+ }
+
+ printf("&%02x:%02x:%02x:%02x:%02x:%02x",
+ a->mask[0], a->mask[1], a->mask[2], a->mask[3], a->mask[4],
+ a->mask[5]);
+}
+
+void
+print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call,
+ int rule_numbers)
+{
+ static const char *actiontypes[] = { "pass", "block", "", "", "", "",
+ "", "", "", "", "", "", "match" };
+
+ int i;
+
+ if (rule_numbers)
+ printf("@%u ", r->nr);
+
+ printf("ether ");
+ if (anchor_call[0]) {
+ if (anchor_call[0] == '_') {
+ printf("anchor");
+ } else
+ printf("anchor \"%s\"", anchor_call);
+ } else {
+ printf("%s", actiontypes[r->action]);
+ }
+ if (r->direction == PF_IN)
+ printf(" in");
+ else if (r->direction == PF_OUT)
+ printf(" out");
+
+ if (r->quick)
+ printf(" quick");
+ if (r->ifname[0]) {
+ if (r->ifnot)
+ printf(" on ! %s", r->ifname);
+ else
+ printf(" on %s", r->ifname);
+ }
+ if (r->bridge_to[0])
+ printf(" bridge-to %s", r->bridge_to);
+ if (r->proto)
+ printf(" proto 0x%04x", r->proto);
+
+ if (r->src.isset) {
+ printf(" from ");
+ print_eth_addr(&r->src);
+ }
+ if (r->dst.isset) {
+ printf(" to ");
+ print_eth_addr(&r->dst);
+ }
+ printf(" l3");
+ print_fromto(&r->ipsrc, PF_OSFP_ANY, &r->ipdst,
+ r->proto == ETHERTYPE_IP ? AF_INET : AF_INET6, 0,
+ 0, 0);
+
+ i = 0;
+ while (r->label[i][0])
+ printf(" label \"%s\"", r->label[i++]);
+ if (r->ridentifier)
+ printf(" ridentifier %u", r->ridentifier);
+
+ if (r->qname[0])
+ printf(" queue %s", r->qname);
+ if (r->tagname[0])
+ printf(" tag %s", r->tagname);
+ if (r->match_tagname[0]) {
+ if (r->match_tag_not)
+ printf(" !");
+ printf(" tagged %s", r->match_tagname);
+ }
+ if (r->dnpipe)
+ printf(" %s %d",
+ r->dnflags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue",
+ r->dnpipe);
+}
+
+void
+print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric)
+{
+ static const char *actiontypes[] = { "pass", "block", "scrub",
+ "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr",
+ "synproxy drop", "defer", "match", "af-rt", "route-to" };
+ static const char *anchortypes[] = { "anchor", "anchor", "anchor",
+ "anchor", "nat-anchor", "nat-anchor", "binat-anchor",
+ "binat-anchor", "rdr-anchor", "rdr-anchor" };
+ int i, ropts;
+ int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG);
+ char *p;
+
+ if ((r->rule_flag & PFRULE_EXPIRED) && (!verbose))
+ return;
+
+ if (verbose)
+ printf("@%d ", r->nr);
+ if (anchor_call[0]) {
+ if (r->action >= nitems(anchortypes)) {
+ printf("anchor(%d)", r->action);
+ } else {
+ p = strrchr(anchor_call, '/');
+ if (p ? p[1] == '_' : anchor_call[0] == '_')
+ printf("%s", anchortypes[r->action]);
+ else
+ printf("%s \"%s\"", anchortypes[r->action],
+ anchor_call);
+ }
+ } else {
+ if (r->action >= nitems(actiontypes))
+ printf("action(%d)", r->action);
+ else
+ printf("%s", actiontypes[r->action]);
+ }
+ if (r->action == PF_DROP) {
+ if (r->rule_flag & PFRULE_RETURN)
+ printf(" return");
+ else if (r->rule_flag & PFRULE_RETURNRST) {
+ if (!r->return_ttl)
+ printf(" return-rst");
+ else
+ printf(" return-rst(ttl %d)", r->return_ttl);
+ } else if (r->rule_flag & PFRULE_RETURNICMP) {
+ const struct icmpcodeent *ic, *ic6;
+
+ ic = geticmpcodebynumber(r->return_icmp >> 8,
+ r->return_icmp & 255, AF_INET);
+ ic6 = geticmpcodebynumber(r->return_icmp6 >> 8,
+ r->return_icmp6 & 255, AF_INET6);
+
+ switch (r->af) {
+ case AF_INET:
+ printf(" return-icmp");
+ if (ic == NULL)
+ printf("(%u)", r->return_icmp & 255);
+ else
+ printf("(%s)", ic->name);
+ break;
+ case AF_INET6:
+ printf(" return-icmp6");
+ if (ic6 == NULL)
+ printf("(%u)", r->return_icmp6 & 255);
+ else
+ printf("(%s)", ic6->name);
+ break;
+ default:
+ printf(" return-icmp");
+ if (ic == NULL)
+ printf("(%u, ", r->return_icmp & 255);
+ else
+ printf("(%s, ", ic->name);
+ if (ic6 == NULL)
+ printf("%u)", r->return_icmp6 & 255);
+ else
+ printf("%s)", ic6->name);
+ break;
+ }
+ } else
+ printf(" drop");
+ }
+ if (r->direction == PF_IN)
+ printf(" in");
+ else if (r->direction == PF_OUT)
+ printf(" out");
+ if (r->log) {
+ printf(" log");
+ if (r->log & ~PF_LOG || r->logif) {
+ int count = 0;
+
+ printf(" (");
+ if (r->log & PF_LOG_ALL)
+ printf("%sall", count++ ? ", " : "");
+ if (r->log & PF_LOG_MATCHES)
+ printf("%smatches", count++ ? ", " : "");
+ if (r->log & PF_LOG_USER)
+ printf("%suser", count++ ? ", " : "");
+ if (r->logif)
+ printf("%sto pflog%u", count++ ? ", " : "",
+ r->logif);
+ printf(")");
+ }
+ }
+ if (r->quick)
+ printf(" quick");
+ if (r->ifname[0]) {
+ if (r->ifnot)
+ printf(" on ! %s", r->ifname);
+ else
+ printf(" on %s", r->ifname);
+ }
+ if (r->rt) {
+ if (r->rt == PF_ROUTETO)
+ printf(" route-to");
+ else if (r->rt == PF_REPLYTO)
+ printf(" reply-to");
+ else if (r->rt == PF_DUPTO)
+ printf(" dup-to");
+ printf(" ");
+ print_pool(&r->route, 0, 0, PF_PASS);
+ }
+ if (r->af) {
+ if (r->af == AF_INET)
+ printf(" inet");
+ else
+ printf(" inet6");
+ }
+ if (r->proto) {
+ const char *protoname;
+
+ if ((protoname = pfctl_proto2name(r->proto)) != NULL)
+ printf(" proto %s", protoname);
+ else
+ printf(" proto %u", r->proto);
+ }
+ print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto,
+ opts, numeric);
+ if (r->rcv_ifname[0])
+ printf(" %sreceived-on %s", r->rcvifnot ? "!" : "",
+ r->rcv_ifname);
+ if (r->uid.op)
+ print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user");
+ if (r->gid.op)
+ print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group");
+ if (r->flags || r->flagset) {
+ printf(" flags ");
+ print_flags(r->flags);
+ printf("/");
+ print_flags(r->flagset);
+ } else if ((r->action == PF_PASS || r->action == PF_MATCH) &&
+ (!r->proto || r->proto == IPPROTO_TCP) &&
+ !(r->rule_flag & PFRULE_FRAGMENT) &&
+ !anchor_call[0] && r->keep_state)
+ printf(" flags any");
+ if (r->type) {
+ const struct icmptypeent *it;
+
+ it = geticmptypebynumber(r->type-1, r->af);
+ if (r->af != AF_INET6)
+ printf(" icmp-type");
+ else
+ printf(" icmp6-type");
+ if (it != NULL)
+ printf(" %s", it->name);
+ else
+ printf(" %u", r->type-1);
+ if (r->code) {
+ const struct icmpcodeent *ic;
+
+ ic = geticmpcodebynumber(r->type-1, r->code-1, r->af);
+ if (ic != NULL)
+ printf(" code %s", ic->name);
+ else
+ printf(" code %u", r->code-1);
+ }
+ }
+ if (r->tos)
+ printf(" tos 0x%2.2x", r->tos);
+ if (r->prio)
+ printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio);
+ if (r->pktrate.limit)
+ printf(" max-pkt-rate %u/%u", r->pktrate.limit,
+ r->pktrate.seconds);
+ if (r->max_pkt_size)
+ printf( " max-pkt-size %u", r->max_pkt_size);
+ if (r->scrub_flags & PFSTATE_SETMASK) {
+ char *comma = "";
+ printf(" set (");
+ if (r->scrub_flags & PFSTATE_SETPRIO) {
+ if (r->set_prio[0] == r->set_prio[1])
+ printf("%s prio %u", comma, r->set_prio[0]);
+ else
+ printf("%s prio(%u, %u)", comma, r->set_prio[0],
+ r->set_prio[1]);
+ comma = ",";
+ }
+ if (r->scrub_flags & PFSTATE_SETTOS) {
+ printf("%s tos 0x%2.2x", comma, r->set_tos);
+ comma = ",";
+ }
+ printf(" )");
+ }
+ if (!r->keep_state && r->action == PF_PASS && !anchor_call[0])
+ printf(" no state");
+ else if (r->keep_state == PF_STATE_NORMAL)
+ printf(" keep state");
+ else if (r->keep_state == PF_STATE_MODULATE)
+ printf(" modulate state");
+ else if (r->keep_state == PF_STATE_SYNPROXY)
+ printf(" synproxy state");
+ if (r->prob) {
+ char buf[20];
+
+ snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0));
+ for (i = strlen(buf)-1; i > 0; i--) {
+ if (buf[i] == '0')
+ buf[i] = '\0';
+ else {
+ if (buf[i] == '.')
+ buf[i] = '\0';
+ break;
+ }
+ }
+ printf(" probability %s%%", buf);
+ }
+ ropts = 0;
+ if (r->max_states || r->max_src_nodes || r->max_src_states)
+ ropts = 1;
+ if (r->rule_flag & PFRULE_NOSYNC)
+ ropts = 1;
+ if (r->rule_flag & PFRULE_SRCTRACK)
+ ropts = 1;
+ if (r->rule_flag & PFRULE_IFBOUND)
+ ropts = 1;
+ if (r->rule_flag & PFRULE_STATESLOPPY)
+ ropts = 1;
+ if (r->rule_flag & PFRULE_PFLOW)
+ ropts = 1;
+ for (i = 0; !ropts && i < PFTM_MAX; ++i)
+ if (r->timeout[i])
+ ropts = 1;
+ if (ropts) {
+ printf(" (");
+ if (r->max_states) {
+ printf("max %u", r->max_states);
+ ropts = 0;
+ }
+ if (r->rule_flag & PFRULE_NOSYNC) {
+ if (!ropts)
+ printf(", ");
+ printf("no-sync");
+ ropts = 0;
+ }
+ if (r->rule_flag & PFRULE_SRCTRACK) {
+ if (!ropts)
+ printf(", ");
+ printf("source-track");
+ if (r->rule_flag & PFRULE_RULESRCTRACK)
+ printf(" rule");
+ else
+ printf(" global");
+ ropts = 0;
+ }
+ if (r->max_src_states) {
+ if (!ropts)
+ printf(", ");
+ printf("max-src-states %u", r->max_src_states);
+ ropts = 0;
+ }
+ if (r->max_src_conn) {
+ if (!ropts)
+ printf(", ");
+ printf("max-src-conn %u", r->max_src_conn);
+ ropts = 0;
+ }
+ if (r->max_src_conn_rate.limit) {
+ if (!ropts)
+ printf(", ");
+ printf("max-src-conn-rate %u/%u",
+ r->max_src_conn_rate.limit,
+ r->max_src_conn_rate.seconds);
+ ropts = 0;
+ }
+ if (r->max_src_nodes) {
+ if (!ropts)
+ printf(", ");
+ printf("max-src-nodes %u", r->max_src_nodes);
+ ropts = 0;
+ }
+ if (r->overload_tblname[0]) {
+ if (!ropts)
+ printf(", ");
+ printf("overload <%s>", r->overload_tblname);
+ if (r->flush)
+ printf(" flush");
+ if (r->flush & PF_FLUSH_GLOBAL)
+ printf(" global");
+ }
+ if (r->rule_flag & PFRULE_IFBOUND) {
+ if (!ropts)
+ printf(", ");
+ printf("if-bound");
+ ropts = 0;
+ }
+ if (r->rule_flag & PFRULE_STATESLOPPY) {
+ if (!ropts)
+ printf(", ");
+ printf("sloppy");
+ ropts = 0;
+ }
+ if (r->rule_flag & PFRULE_PFLOW) {
+ if (!ropts)
+ printf(", ");
+ printf("pflow");
+ ropts = 0;
+ }
+ for (i = 0; i < PFTM_MAX; ++i)
+ if (r->timeout[i]) {
+ int j;
+
+ if (!ropts)
+ printf(", ");
+ ropts = 0;
+ for (j = 0; pf_timeouts[j].name != NULL;
+ ++j)
+ if (pf_timeouts[j].timeout == i)
+ break;
+ printf("%s %u", pf_timeouts[j].name == NULL ?
+ "inv.timeout" : pf_timeouts[j].name,
+ r->timeout[i]);
+ }
+ printf(")");
+ }
+ if (r->allow_opts)
+ printf(" allow-opts");
+ if (r->rule_flag & PFRULE_FRAGMENT)
+ printf(" fragment");
+ if (r->action == PF_SCRUB) {
+ /* Scrub flags for old-style scrub. */
+ if (r->rule_flag & PFRULE_NODF)
+ printf(" no-df");
+ if (r->rule_flag & PFRULE_RANDOMID)
+ printf(" random-id");
+ if (r->min_ttl)
+ printf(" min-ttl %d", r->min_ttl);
+ if (r->max_mss)
+ printf(" max-mss %d", r->max_mss);
+ if (r->rule_flag & PFRULE_SET_TOS)
+ printf(" set-tos 0x%2.2x", r->set_tos);
+ if (r->rule_flag & PFRULE_REASSEMBLE_TCP)
+ printf(" reassemble tcp");
+ /* The PFRULE_FRAGMENT_NOREASS is set on all rules by default! */
+ printf(" fragment %sreassemble",
+ r->rule_flag & PFRULE_FRAGMENT_NOREASS ? "no " : "");
+ } else if (r->scrub_flags & PFSTATE_SCRUBMASK || r->min_ttl || r->max_mss) {
+ /* Scrub actions on normal rules. */
+ printf(" scrub(");
+ if (r->scrub_flags & PFSTATE_NODF)
+ printf(" no-df");
+ if (r->scrub_flags & PFSTATE_RANDOMID)
+ printf(" random-id");
+ if (r->min_ttl)
+ printf(" min-ttl %d", r->min_ttl);
+ if (r->scrub_flags & PFSTATE_SETTOS)
+ printf(" set-tos 0x%2.2x", r->set_tos);
+ if (r->scrub_flags & PFSTATE_SCRUB_TCP)
+ printf(" reassemble tcp");
+ if (r->max_mss)
+ printf(" max-mss %d", r->max_mss);
+ printf(")");
+ }
+ i = 0;
+ while (r->label[i][0])
+ printf(" label \"%s\"", r->label[i++]);
+ if (r->ridentifier)
+ printf(" ridentifier %u", r->ridentifier);
+ /* Only dnrpipe as we might do (0, 42) to only queue return traffic. */
+ if (r->dnrpipe)
+ printf(" %s(%d, %d)",
+ r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue",
+ r->dnpipe, r->dnrpipe);
+ else if (r->dnpipe)
+ printf(" %s %d",
+ r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue",
+ r->dnpipe);
+ if (r->rule_flag & PFRULE_ONCE)
+ printf(" once");
+ if (r->qname[0] && r->pqname[0])
+ printf(" queue(%s, %s)", r->qname, r->pqname);
+ else if (r->qname[0])
+ printf(" queue %s", r->qname);
+ if (r->tagname[0])
+ printf(" tag %s", r->tagname);
+ if (r->match_tagname[0]) {
+ if (r->match_tag_not)
+ printf(" !");
+ printf(" tagged %s", r->match_tagname);
+ }
+ if (r->rtableid != -1)
+ printf(" rtable %u", r->rtableid);
+ if (r->divert.port) {
+#ifdef __FreeBSD__
+ printf(" divert-to %u", ntohs(r->divert.port));
+#else
+ if (PF_AZERO(&r->divert.addr, r->af)) {
+ printf(" divert-reply");
+ } else {
+ printf(" divert-to ");
+ print_addr_str(r->af, &r->divert.addr);
+ printf(" port %u", ntohs(r->divert.port));
+ }
+#endif
+ }
+ if (anchor_call[0])
+ return;
+ if (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR) {
+ printf(" -> ");
+ print_pool(&r->rdr, r->rdr.proxy_port[0],
+ r->rdr.proxy_port[1], r->action);
+ } else {
+ if (!TAILQ_EMPTY(&r->nat.list)) {
+ if (r->rule_flag & PFRULE_AFTO) {
+ printf(" af-to %s from ", r->naf == AF_INET ? "inet" : (r->naf == AF_INET6 ? "inet6" : "? "));
+ } else {
+ printf(" nat-to ");
+ }
+ print_pool(&r->nat, r->nat.proxy_port[0],
+ r->nat.proxy_port[1], PF_NAT);
+ }
+ if (!TAILQ_EMPTY(&r->rdr.list)) {
+ if (r->rule_flag & PFRULE_AFTO) {
+ printf(" to ");
+ } else {
+ printf(" rdr-to ");
+ }
+ print_pool(&r->rdr, r->rdr.proxy_port[0],
+ r->rdr.proxy_port[1], PF_RDR);
+ }
+ }
+
+ if (r->rule_flag & PFRULE_EXPIRED) {
+ printf(" # expired");
+
+ if (r->exptime != 0)
+ printf(" %s", ctime(&r->exptime));
+ }
+}
+
+void
+print_tabledef(const char *name, int flags, int addrs,
+ struct node_tinithead *nodes)
+{
+ struct node_tinit *ti, *nti;
+ struct node_host *h;
+
+ printf("table <%s>", name);
+ if (flags & PFR_TFLAG_CONST)
+ printf(" const");
+ if (flags & PFR_TFLAG_PERSIST)
+ printf(" persist");
+ if (flags & PFR_TFLAG_COUNTERS)
+ printf(" counters");
+ SIMPLEQ_FOREACH(ti, nodes, entries) {
+ if (ti->file) {
+ printf(" file \"%s\"", ti->file);
+ continue;
+ }
+ printf(" {");
+ for (;;) {
+ for (h = ti->host; h != NULL; h = h->next) {
+ printf(h->not ? " !" : " ");
+ print_addr(&h->addr, h->af, 0);
+ }
+ nti = SIMPLEQ_NEXT(ti, entries);
+ if (nti != NULL && nti->file == NULL)
+ ti = nti; /* merge lists */
+ else
+ break;
+ }
+ printf(" }");
+ }
+ if (addrs && SIMPLEQ_EMPTY(nodes))
+ printf(" { }");
+ printf("\n");
+}
+
+int
+parse_flags(char *s)
+{
+ char *p, *q;
+ uint16_t f = 0;
+
+ for (p = s; *p; p++) {
+ if ((q = strchr(tcpflags, *p)) == NULL)
+ return -1;
+ else
+ f |= 1 << (q - tcpflags);
+ }
+ return (f ? f : TH_FLAGS);
+}
+
+void
+set_ipmask(struct node_host *h, int bb)
+{
+ struct pf_addr *m, *n;
+ int i, j = 0;
+ uint8_t b;
+
+ m = &h->addr.v.a.mask;
+ memset(m, 0, sizeof(*m));
+
+ if (bb == -1)
+ b = h->af == AF_INET ? 32 : 128;
+ else
+ b = bb;
+
+ while (b >= 32) {
+ m->addr32[j++] = 0xffffffff;
+ b -= 32;
+ }
+ for (i = 31; i > 31-b; --i)
+ m->addr32[j] |= (1 << i);
+ if (b)
+ m->addr32[j] = htonl(m->addr32[j]);
+
+ /* Mask off bits of the address that will never be used. */
+ n = &h->addr.v.a.addr;
+ if (h->addr.type == PF_ADDR_ADDRMASK)
+ for (i = 0; i < 4; i++)
+ n->addr32[i] = n->addr32[i] & m->addr32[i];
+}
+
+int
+check_netmask(struct node_host *h, sa_family_t af)
+{
+ struct node_host *n = NULL;
+ struct pf_addr *m;
+
+ for (n = h; n != NULL; n = n->next) {
+ if (h->addr.type == PF_ADDR_TABLE)
+ continue;
+ m = &h->addr.v.a.mask;
+ /* netmasks > 32 bit are invalid on v4 */
+ if (af == AF_INET &&
+ (m->addr32[1] || m->addr32[2] || m->addr32[3])) {
+ fprintf(stderr, "netmask %u invalid for IPv4 address\n",
+ unmask(m));
+ return (1);
+ }
+ }
+ return (0);
+}
+
+struct node_host *
+gen_dynnode(struct node_host *h, sa_family_t af)
+{
+ struct node_host *n;
+
+ if (h->addr.type != PF_ADDR_DYNIFTL)
+ return (NULL);
+
+ if ((n = calloc(1, sizeof(*n))) == NULL)
+ return (NULL);
+ bcopy(h, n, sizeof(*n));
+ n->ifname = NULL;
+ n->next = NULL;
+ n->tail = NULL;
+
+ /* fix up netmask */
+ if (af == AF_INET && unmask(&n->addr.v.a.mask) > 32)
+ set_ipmask(n, 32);
+
+ return (n);
+}
+
+/* interface lookup routines */
+
+static struct node_host *iftab;
+
+/*
+ * Retrieve the list of groups this interface is a member of and make sure
+ * each group is in the group map.
+ */
+static void
+ifa_add_groups_to_map(char *ifa_name)
+{
+ int s, len;
+ struct ifgroupreq ifgr;
+ struct ifg_req *ifg;
+
+ s = get_query_socket();
+
+ /* Get size of group list for this interface */
+ memset(&ifgr, 0, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, ifa_name, IFNAMSIZ);
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ /* Retrieve group list for this interface */
+ len = ifgr.ifgr_len;
+ ifgr.ifgr_groups =
+ (struct ifg_req *)calloc(len / sizeof(struct ifg_req),
+ sizeof(struct ifg_req));
+ if (ifgr.ifgr_groups == NULL)
+ err(1, "calloc");
+ if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGROUP");
+
+ ifg = ifgr.ifgr_groups;
+ for (; ifg && len >= sizeof(struct ifg_req); ifg++) {
+ len -= sizeof(struct ifg_req);
+ if (strcmp(ifg->ifgrq_group, "all")) {
+ ENTRY item;
+ ENTRY *ret_item;
+ int *answer;
+
+ item.key = ifg->ifgrq_group;
+ if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) {
+ struct ifgroupreq ifgr2;
+
+ /* Don't know the answer yet */
+ if ((answer = malloc(sizeof(int))) == NULL)
+ err(1, "malloc");
+
+ bzero(&ifgr2, sizeof(ifgr2));
+ strlcpy(ifgr2.ifgr_name, ifg->ifgrq_group,
+ sizeof(ifgr2.ifgr_name));
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr2) == 0)
+ *answer = ifgr2.ifgr_len;
+ else
+ *answer = 0;
+
+ item.key = strdup(ifg->ifgrq_group);
+ item.data = answer;
+ if (hsearch_r(item, ENTER, &ret_item,
+ &isgroup_map) == 0)
+ err(1, "interface group query response"
+ " map insert");
+ }
+ }
+ }
+ free(ifgr.ifgr_groups);
+}
+
+void
+ifa_load(void)
+{
+ struct ifaddrs *ifap, *ifa;
+ struct node_host *n = NULL, *h = NULL;
+
+ if (getifaddrs(&ifap) < 0)
+ err(1, "getifaddrs");
+
+ for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_addr == NULL ||
+ !(ifa->ifa_addr->sa_family == AF_INET ||
+ ifa->ifa_addr->sa_family == AF_INET6 ||
+ ifa->ifa_addr->sa_family == AF_LINK))
+ continue;
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "%s: calloc", __func__);
+ n->af = ifa->ifa_addr->sa_family;
+ n->ifa_flags = ifa->ifa_flags;
+#ifdef __KAME__
+ if (n->af == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)
+ ifa->ifa_addr)->sin6_addr) &&
+ ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id ==
+ 0) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+ sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 |
+ sin6->sin6_addr.s6_addr[3];
+ sin6->sin6_addr.s6_addr[2] = 0;
+ sin6->sin6_addr.s6_addr[3] = 0;
+ }
+#endif
+ n->ifindex = 0;
+ if (n->af == AF_LINK) {
+ n->ifindex = ((struct sockaddr_dl *)
+ ifa->ifa_addr)->sdl_index;
+ ifa_add_groups_to_map(ifa->ifa_name);
+ } else {
+ copy_satopfaddr(&n->addr.v.a.addr, ifa->ifa_addr);
+ ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family;
+ copy_satopfaddr(&n->addr.v.a.mask, ifa->ifa_netmask);
+ if (ifa->ifa_broadaddr != NULL) {
+ ifa->ifa_broadaddr->sa_family = ifa->ifa_addr->sa_family;
+ copy_satopfaddr(&n->bcast, ifa->ifa_broadaddr);
+ }
+ if (ifa->ifa_dstaddr != NULL) {
+ ifa->ifa_dstaddr->sa_family = ifa->ifa_addr->sa_family;
+ copy_satopfaddr(&n->peer, ifa->ifa_dstaddr);
+ }
+ if (n->af == AF_INET6)
+ n->ifindex = ((struct sockaddr_in6 *)
+ ifa->ifa_addr) ->sin6_scope_id;
+ }
+ if ((n->ifname = strdup(ifa->ifa_name)) == NULL)
+ err(1, "%s: strdup", __func__);
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+
+ iftab = h;
+ freeifaddrs(ifap);
+}
+
+static int
+get_socket_domain(void)
+{
+ int sdom;
+
+ sdom = AF_UNSPEC;
+#ifdef WITH_INET6
+ if (sdom == AF_UNSPEC && feature_present("inet6"))
+ sdom = AF_INET6;
+#endif
+#ifdef WITH_INET
+ if (sdom == AF_UNSPEC && feature_present("inet"))
+ sdom = AF_INET;
+#endif
+ if (sdom == AF_UNSPEC)
+ sdom = AF_LINK;
+
+ return (sdom);
+}
+
+int
+get_query_socket(void)
+{
+ static int s = -1;
+
+ if (s == -1) {
+ if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1)
+ err(1, "socket");
+ }
+
+ return (s);
+}
+
+/*
+ * Returns the response len if the name is a group, otherwise returns 0.
+ */
+static int
+is_a_group(char *name)
+{
+ ENTRY item;
+ ENTRY *ret_item;
+
+ item.key = name;
+ if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0)
+ return (0);
+
+ return (*(int *)ret_item->data);
+}
+
+unsigned int
+ifa_nametoindex(const char *ifa_name)
+{
+ struct node_host *p;
+
+ for (p = iftab; p; p = p->next) {
+ if (p->af == AF_LINK && strcmp(p->ifname, ifa_name) == 0)
+ return (p->ifindex);
+ }
+ errno = ENXIO;
+ return (0);
+}
+
+char *
+ifa_indextoname(unsigned int ifindex, char *ifa_name)
+{
+ struct node_host *p;
+
+ for (p = iftab; p; p = p->next) {
+ if (p->af == AF_LINK && ifindex == p->ifindex) {
+ strlcpy(ifa_name, p->ifname, IFNAMSIZ);
+ return (ifa_name);
+ }
+ }
+ errno = ENXIO;
+ return (NULL);
+}
+
+struct node_host *
+ifa_exists(char *ifa_name)
+{
+ struct node_host *n;
+
+ if (iftab == NULL)
+ ifa_load();
+
+ /* check whether this is a group */
+ if (is_a_group(ifa_name)) {
+ /* fake a node_host */
+ if ((n = calloc(1, sizeof(*n))) == NULL)
+ err(1, "calloc");
+ if ((n->ifname = strdup(ifa_name)) == NULL)
+ err(1, "strdup");
+ return (n);
+ }
+
+ for (n = iftab; n; n = n->next) {
+ if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ))
+ return (n);
+ }
+
+ return (NULL);
+}
+
+struct node_host *
+ifa_grouplookup(char *ifa_name, int flags)
+{
+ struct ifg_req *ifg;
+ struct ifgroupreq ifgr;
+ int s, len;
+ struct node_host *n, *h = NULL;
+
+ s = get_query_socket();
+ len = is_a_group(ifa_name);
+ if (len == 0)
+ return (NULL);
+ bzero(&ifgr, sizeof(ifgr));
+ strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name));
+ ifgr.ifgr_len = len;
+ if ((ifgr.ifgr_groups = calloc(1, len)) == NULL)
+ err(1, "calloc");
+ if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1)
+ err(1, "SIOCGIFGMEMB");
+
+ for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req);
+ ifg++) {
+ len -= sizeof(struct ifg_req);
+ if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL)
+ continue;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n->tail;
+ }
+ }
+ free(ifgr.ifgr_groups);
+
+ return (h);
+}
+
+struct node_host *
+ifa_lookup(char *ifa_name, int flags)
+{
+ struct node_host *p = NULL, *h = NULL, *n = NULL;
+ int got4 = 0, got6 = 0;
+ const char *last_if = NULL;
+
+ /* first load iftab and isgroup_map */
+ if (iftab == NULL)
+ ifa_load();
+
+ if ((h = ifa_grouplookup(ifa_name, flags)) != NULL)
+ return (h);
+
+ if (!strncmp(ifa_name, "self", IFNAMSIZ))
+ ifa_name = NULL;
+
+ for (p = iftab; p; p = p->next) {
+ if (ifa_skip_if(ifa_name, p))
+ continue;
+ if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET)
+ continue;
+ if ((flags & PFI_AFLAG_BROADCAST) &&
+ !(p->ifa_flags & IFF_BROADCAST))
+ continue;
+ if ((flags & PFI_AFLAG_BROADCAST) && p->bcast.v4.s_addr == 0)
+ continue;
+ if ((flags & PFI_AFLAG_PEER) &&
+ !(p->ifa_flags & IFF_POINTOPOINT))
+ continue;
+ if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0)
+ continue;
+ if (last_if == NULL || strcmp(last_if, p->ifname))
+ got4 = got6 = 0;
+ last_if = p->ifname;
+ if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4)
+ continue;
+ if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 &&
+ IN6_IS_ADDR_LINKLOCAL(&p->addr.v.a.addr.v6))
+ continue;
+ if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6)
+ continue;
+ if (p->af == AF_INET)
+ got4 = 1;
+ else
+ got6 = 1;
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "%s: calloc", __func__);
+ n->af = p->af;
+ if (flags & PFI_AFLAG_BROADCAST)
+ memcpy(&n->addr.v.a.addr, &p->bcast,
+ sizeof(struct pf_addr));
+ else if (flags & PFI_AFLAG_PEER)
+ memcpy(&n->addr.v.a.addr, &p->peer,
+ sizeof(struct pf_addr));
+ else
+ memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr,
+ sizeof(struct pf_addr));
+ if (flags & PFI_AFLAG_NETWORK)
+ set_ipmask(n, unmask(&p->addr.v.a.mask));
+ else
+ set_ipmask(n, -1);
+ n->ifindex = p->ifindex;
+ n->ifname = strdup(p->ifname);
+
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+ return (h);
+}
+
+int
+ifa_skip_if(const char *filter, struct node_host *p)
+{
+ int n;
+
+ if (p->af != AF_INET && p->af != AF_INET6)
+ return (1);
+ if (filter == NULL || !*filter)
+ return (0);
+ if (!strcmp(p->ifname, filter))
+ return (0); /* exact match */
+ n = strlen(filter);
+ if (n < 1 || n >= IFNAMSIZ)
+ return (1); /* sanity check */
+ if (filter[n-1] >= '0' && filter[n-1] <= '9')
+ return (1); /* only do exact match in that case */
+ if (strncmp(p->ifname, filter, n))
+ return (1); /* prefix doesn't match */
+ return (p->ifname[n] < '0' || p->ifname[n] > '9');
+}
+
+
+struct node_host *
+host(const char *s, int opts)
+{
+ struct node_host *h = NULL;
+ int mask = -1;
+ char *p, *ps;
+ const char *errstr;
+
+ if ((p = strchr(s, '/')) != NULL) {
+ mask = strtonum(p+1, 0, 128, &errstr);
+ if (errstr) {
+ fprintf(stderr, "netmask is %s: %s\n", errstr, p);
+ goto error;
+ }
+ if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL)
+ err(1, "%s: malloc", __func__);
+ strlcpy(ps, s, strlen(s) - strlen(p) + 1);
+ } else {
+ if ((ps = strdup(s)) == NULL)
+ err(1, "%s: strdup", __func__);
+ }
+
+ if ((h = host_ip(ps, mask)) == NULL &&
+ (h = host_if(ps, mask)) == NULL &&
+ (h = host_dns(ps, mask, (opts & PF_OPT_NODNS))) == NULL) {
+ fprintf(stderr, "no IP address found for %s\n", s);
+ goto error;
+ }
+
+error:
+ free(ps);
+ return (h);
+}
+
+struct node_host *
+host_if(const char *s, int mask)
+{
+ struct node_host *n, *h = NULL;
+ char *p, *ps;
+ int flags = 0;
+
+ if ((ps = strdup(s)) == NULL)
+ err(1, "host_if: strdup");
+ while ((p = strrchr(ps, ':')) != NULL) {
+ if (!strcmp(p+1, "network"))
+ flags |= PFI_AFLAG_NETWORK;
+ else if (!strcmp(p+1, "broadcast"))
+ flags |= PFI_AFLAG_BROADCAST;
+ else if (!strcmp(p+1, "peer"))
+ flags |= PFI_AFLAG_PEER;
+ else if (!strcmp(p+1, "0"))
+ flags |= PFI_AFLAG_NOALIAS;
+ else
+ goto error;
+ *p = '\0';
+ }
+ if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */
+ fprintf(stderr, "illegal combination of interface modifiers\n");
+ goto error;
+ }
+ if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) {
+ fprintf(stderr, "network or broadcast lookup, but "
+ "extra netmask given\n");
+ goto error;
+ }
+ if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) {
+ /* interface with this name exists */
+ h = ifa_lookup(ps, flags);
+ if (mask > -1)
+ for (n = h; n != NULL; n = n->next)
+ set_ipmask(n, mask);
+ }
+
+error:
+ free(ps);
+ return (h);
+}
+
+struct node_host *
+host_ip(const char *s, int mask)
+{
+ struct addrinfo hints, *res;
+ struct node_host *h = NULL;
+
+ h = calloc(1, sizeof(*h));
+ if (h == NULL)
+ err(1, "%s: calloc", __func__);
+ if (mask != -1) {
+ /* Try to parse 10/8 */
+ h->af = AF_INET;
+ if (inet_net_pton(AF_INET, s, &h->addr.v.a.addr.v4,
+ sizeof(h->addr.v.a.addr.v4)) != -1)
+ goto out;
+ }
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM; /*dummy*/
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(s, NULL, &hints, &res) == 0) {
+ h->af = res->ai_family;
+ copy_satopfaddr(&h->addr.v.a.addr, res->ai_addr);
+ if (h->af == AF_INET6)
+ h->ifindex =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ freeaddrinfo(res);
+ } else {
+ free(h);
+ return (NULL);
+ }
+out:
+ set_ipmask(h, mask);
+ h->ifname = NULL;
+ h->next = NULL;
+ h->tail = h;
+
+ return (h);
+}
+
+struct node_host *
+host_dns(const char *s, int mask, int numeric)
+{
+ struct addrinfo hints, *res0, *res;
+ struct node_host *n, *h = NULL;
+ int noalias = 0, got4 = 0, got6 = 0;
+ char *p, *ps;
+
+ if ((ps = strdup(s)) == NULL)
+ err(1, "host_dns: strdup");
+ if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) {
+ noalias = 1;
+ *p = '\0';
+ }
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM; /* DUMMY */
+ if (numeric)
+ hints.ai_flags = AI_NUMERICHOST;
+ if (getaddrinfo(ps, NULL, &hints, &res0) != 0)
+ goto error;
+
+ for (res = res0; res; res = res->ai_next) {
+ if (res->ai_family != AF_INET &&
+ res->ai_family != AF_INET6)
+ continue;
+ if (noalias) {
+ if (res->ai_family == AF_INET) {
+ if (got4)
+ continue;
+ got4 = 1;
+ } else {
+ if (got6)
+ continue;
+ got6 = 1;
+ }
+ }
+ n = calloc(1, sizeof(struct node_host));
+ if (n == NULL)
+ err(1, "host_dns: calloc");
+ n->ifname = NULL;
+ n->af = res->ai_family;
+ copy_satopfaddr(&n->addr.v.a.addr, res->ai_addr);
+ if (res->ai_family == AF_INET6)
+ n->ifindex =
+ ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id;
+ set_ipmask(n, mask);
+ n->next = NULL;
+ n->tail = n;
+ if (h == NULL)
+ h = n;
+ else {
+ h->tail->next = n;
+ h->tail = n;
+ }
+ }
+ freeaddrinfo(res0);
+error:
+ free(ps);
+
+ return (h);
+}
+
+/*
+ * convert a hostname to a list of addresses and put them in the given buffer.
+ * test:
+ * if set to 1, only simple addresses are accepted (no netblock, no "!").
+ */
+int
+append_addr(struct pfr_buffer *b, char *s, int test, int opts)
+{
+ char *r;
+ struct node_host *h, *n;
+ int rv, not = 0;
+
+ for (r = s; *r == '!'; r++)
+ not = !not;
+ if ((n = host(r, opts)) == NULL) {
+ errno = 0;
+ return (-1);
+ }
+ rv = append_addr_host(b, n, test, not);
+ do {
+ h = n;
+ n = n->next;
+ free(h);
+ } while (n != NULL);
+ return (rv);
+}
+
+/*
+ * same as previous function, but with a pre-parsed input and the ability
+ * to "negate" the result. Does not free the node_host list.
+ * not:
+ * setting it to 1 is equivalent to adding "!" in front of parameter s.
+ */
+int
+append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not)
+{
+ int bits;
+ struct pfr_addr addr;
+
+ do {
+ bzero(&addr, sizeof(addr));
+ addr.pfra_not = n->not ^ not;
+ addr.pfra_af = n->af;
+ addr.pfra_net = unmask(&n->addr.v.a.mask);
+ switch (n->af) {
+ case AF_INET:
+ addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0];
+ bits = 32;
+ break;
+ case AF_INET6:
+ memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6,
+ sizeof(struct in6_addr));
+ bits = 128;
+ break;
+ default:
+ errno = EINVAL;
+ return (-1);
+ }
+ if ((test && (not || addr.pfra_net != bits)) ||
+ addr.pfra_net > bits) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (pfr_buf_add(b, &addr))
+ return (-1);
+ } while ((n = n->next) != NULL);
+
+ return (0);
+}
+
+int
+pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor)
+{
+ struct pfioc_trans_e trans;
+
+ bzero(&trans, sizeof(trans));
+ trans.rs_num = rs_num;
+ if (strlcpy(trans.anchor, anchor,
+ sizeof(trans.anchor)) >= sizeof(trans.anchor))
+ errx(1, "pfctl_add_trans: strlcpy");
+
+ return pfr_buf_add(buf, &trans);
+}
+
+u_int32_t
+pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor)
+{
+ struct pfioc_trans_e *p;
+
+ PFRB_FOREACH(p, buf)
+ if (rs_num == p->rs_num && !strcmp(anchor, p->anchor))
+ return (p->ticket);
+ errx(1, "pfctl_get_ticket: assertion failed");
+}
+
+int
+pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from)
+{
+ struct pfioc_trans trans;
+
+ bzero(&trans, sizeof(trans));
+ trans.size = buf->pfrb_size - from;
+ trans.esize = sizeof(struct pfioc_trans_e);
+ trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from;
+ return ioctl(dev, cmd, &trans);
+}
diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h
new file mode 100644
index 000000000000..44ddfb45fbe1
--- /dev/null
+++ b/sbin/pfctl/pfctl_parser.h
@@ -0,0 +1,392 @@
+/* $OpenBSD: pfctl_parser.h,v 1.86 2006/10/31 23:46:25 mcbride Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2001 Daniel Hartmeier
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _PFCTL_PARSER_H_
+#define _PFCTL_PARSER_H_
+
+#include <libpfctl.h>
+
+#include <pfctl.h>
+
+#define PF_OSFP_FILE "/etc/pf.os"
+
+#define PF_OPT_DISABLE 0x00001
+#define PF_OPT_ENABLE 0x00002
+#define PF_OPT_VERBOSE 0x00004
+#define PF_OPT_NOACTION 0x00008
+#define PF_OPT_QUIET 0x00010
+#define PF_OPT_CLRRULECTRS 0x00020
+#define PF_OPT_USEDNS 0x00040
+#define PF_OPT_VERBOSE2 0x00080
+#define PF_OPT_DUMMYACTION 0x00100
+#define PF_OPT_DEBUG 0x00200
+#define PF_OPT_SHOWALL 0x00400
+#define PF_OPT_OPTIMIZE 0x00800
+#define PF_OPT_NUMERIC 0x01000
+#define PF_OPT_MERGE 0x02000
+#define PF_OPT_RECURSE 0x04000
+#define PF_OPT_KILLMATCH 0x08000
+#define PF_OPT_NODNS 0x10000
+#define PF_OPT_IGNFAIL 0x20000
+#define PF_OPT_CALLSHOW 0x40000
+
+#define PF_NAT_PROXY_PORT_LOW 50001
+#define PF_NAT_PROXY_PORT_HIGH 65535
+
+#define PF_OPTIMIZE_BASIC 0x0001
+#define PF_OPTIMIZE_PROFILE 0x0002
+
+#define FCNT_NAMES { \
+ "searches", \
+ "inserts", \
+ "removals", \
+ NULL \
+}
+
+struct pfr_buffer; /* forward definition */
+
+
+struct pfctl {
+ int dev;
+ struct pfctl_handle *h;
+ int opts;
+ int optimize;
+ int loadopt;
+ int asd; /* anchor stack depth */
+ int bn; /* brace number */
+ int tdirty; /* kernel dirty */
+#define PFCTL_ANCHOR_STACK_DEPTH 64
+ struct pfctl_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH];
+ struct pfioc_pooladdr paddr;
+ struct pfioc_altq *paltq;
+ struct pfioc_queue *pqueue;
+ struct pfr_buffer *trans;
+ struct pfctl_anchor *anchor, *alast;
+ struct pfr_ktablehead pfr_ktlast;
+ int eth_nr;
+ struct pfctl_eth_anchor *eanchor, *ealast;
+ struct pfctl_eth_anchor *eastack[PFCTL_ANCHOR_STACK_DEPTH];
+ u_int32_t eth_ticket;
+ const char *ruleset;
+
+ /* 'set foo' options */
+ u_int32_t timeout[PFTM_MAX];
+ u_int32_t limit[PF_LIMIT_MAX];
+ u_int32_t debug;
+ u_int32_t hostid;
+ u_int32_t reassemble;
+ char *ifname;
+ bool keep_counters;
+ u_int8_t syncookies;
+ u_int8_t syncookieswat[2]; /* lowat, highwat, in % */
+ u_int8_t syncookieswat_set;
+
+ u_int8_t timeout_set[PFTM_MAX];
+ u_int8_t limit_set[PF_LIMIT_MAX];
+ u_int8_t debug_set;
+ u_int8_t hostid_set;
+ u_int8_t ifname_set;
+ u_int8_t reass_set;
+};
+
+struct node_if {
+ char ifname[IFNAMSIZ];
+ u_int8_t not;
+ u_int8_t dynamic; /* antispoof */
+ u_int ifa_flags;
+ struct node_if *next;
+ struct node_if *tail;
+};
+
+struct node_host {
+ struct pf_addr_wrap addr;
+ struct pf_addr bcast;
+ struct pf_addr peer;
+ sa_family_t af;
+ u_int8_t not;
+ u_int32_t ifindex; /* link-local IPv6 addrs */
+ char *ifname;
+ u_int ifa_flags;
+ struct node_host *next;
+ struct node_host *tail;
+};
+
+void freehostlist(struct node_host *);
+
+struct node_mac {
+ u_int8_t mac[ETHER_ADDR_LEN];
+ u_int8_t mask[ETHER_ADDR_LEN];
+ bool neg;
+ bool isset;
+ struct node_mac *next;
+ struct node_mac *tail;
+};
+
+struct node_os {
+ char *os;
+ pf_osfp_t fingerprint;
+ struct node_os *next;
+ struct node_os *tail;
+};
+
+struct node_queue_bw {
+ u_int64_t bw_absolute;
+ u_int16_t bw_percent;
+};
+
+struct node_hfsc_sc {
+ struct node_queue_bw m1; /* slope of 1st segment; bps */
+ u_int d; /* x-projection of m1; msec */
+ struct node_queue_bw m2; /* slope of 2nd segment; bps */
+ u_int8_t used;
+};
+
+struct node_hfsc_opts {
+ struct node_hfsc_sc realtime;
+ struct node_hfsc_sc linkshare;
+ struct node_hfsc_sc upperlimit;
+ int flags;
+};
+
+struct node_fairq_sc {
+ struct node_queue_bw m1; /* slope of 1st segment; bps */
+ u_int d; /* x-projection of m1; msec */
+ struct node_queue_bw m2; /* slope of 2nd segment; bps */
+ u_int8_t used;
+};
+
+struct node_fairq_opts {
+ struct node_fairq_sc linkshare;
+ struct node_queue_bw hogs_bw;
+ u_int nbuckets;
+ int flags;
+};
+
+struct node_queue_opt {
+ int qtype;
+ union {
+ struct cbq_opts cbq_opts;
+ struct codel_opts codel_opts;
+ struct priq_opts priq_opts;
+ struct node_hfsc_opts hfsc_opts;
+ struct node_fairq_opts fairq_opts;
+ } data;
+};
+
+#define QPRI_BITSET_SIZE 256
+__BITSET_DEFINE(qpri_bitset, QPRI_BITSET_SIZE);
+LIST_HEAD(gen_sc, segment);
+
+struct pfctl_altq {
+ struct pf_altq pa;
+ struct {
+ STAILQ_ENTRY(pfctl_altq) link;
+ u_int64_t bwsum;
+ struct qpri_bitset qpris;
+ int children;
+ int root_classes;
+ int default_classes;
+ struct gen_sc lssc;
+ struct gen_sc rtsc;
+ } meta;
+};
+
+struct pfctl_watermarks {
+ uint32_t hi;
+ uint32_t lo;
+};
+
+#ifdef __FreeBSD__
+/*
+ * XXX
+ * Absolutely this is not correct location to define this.
+ * Should we use an another sperate header file?
+ */
+#define SIMPLEQ_HEAD STAILQ_HEAD
+#define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER
+#define SIMPLEQ_ENTRY STAILQ_ENTRY
+#define SIMPLEQ_FIRST STAILQ_FIRST
+#define SIMPLEQ_END(head) NULL
+#define SIMPLEQ_EMPTY STAILQ_EMPTY
+#define SIMPLEQ_NEXT STAILQ_NEXT
+/*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/
+#define SIMPLEQ_FOREACH(var, head, field) \
+ for((var) = SIMPLEQ_FIRST(head); \
+ (var) != SIMPLEQ_END(head); \
+ (var) = SIMPLEQ_NEXT(var, field))
+#define SIMPLEQ_INIT STAILQ_INIT
+#define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD
+#define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL
+#define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER
+#define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD
+#endif
+SIMPLEQ_HEAD(node_tinithead, node_tinit);
+struct node_tinit { /* table initializer */
+ SIMPLEQ_ENTRY(node_tinit) entries;
+ struct node_host *host;
+ char *file;
+};
+
+
+/* optimizer created tables */
+struct pf_opt_tbl {
+ char pt_name[PF_TABLE_NAME_SIZE];
+ int pt_rulecount;
+ int pt_generated;
+ uint32_t pt_refcnt;
+ struct node_tinithead pt_nodes;
+ struct pfr_buffer *pt_buf;
+};
+
+/* optimizer pf_rule container */
+struct pf_opt_rule {
+ struct pfctl_rule por_rule;
+ struct pf_opt_tbl *por_src_tbl;
+ struct pf_opt_tbl *por_dst_tbl;
+ u_int64_t por_profile_count;
+ TAILQ_ENTRY(pf_opt_rule) por_entry;
+ TAILQ_ENTRY(pf_opt_rule) por_skip_entry[PF_SKIP_COUNT];
+};
+
+TAILQ_HEAD(pf_opt_queue, pf_opt_rule);
+
+struct pfr_uktable;
+
+void copy_satopfaddr(struct pf_addr *, struct sockaddr *);
+
+int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *);
+int pfctl_optimize_ruleset(struct pfctl *, struct pfctl_ruleset *);
+
+void pfctl_init_rule(struct pfctl_rule *r);
+void pfctl_append_rule(struct pfctl *, struct pfctl_rule *);
+int pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *,
+ const char *);
+int pfctl_add_altq(struct pfctl *, struct pf_altq *);
+int pfctl_add_pool(struct pfctl *, struct pfctl_pool *, int);
+void pfctl_move_pool(struct pfctl_pool *, struct pfctl_pool *);
+void pfctl_clear_pool(struct pfctl_pool *);
+
+int pfctl_apply_timeout(struct pfctl *, const char *, int, int);
+int pfctl_set_reassembly(struct pfctl *, int, int);
+int pfctl_set_optimization(struct pfctl *, const char *);
+int pfctl_apply_limit(struct pfctl *, const char *, unsigned int);
+int pfctl_set_logif(struct pfctl *, char *);
+void pfctl_set_hostid(struct pfctl *, u_int32_t);
+int pfctl_do_set_debug(struct pfctl *, char *);
+int pfctl_set_interface_flags(struct pfctl *, char *, int, int);
+int pfctl_cfg_syncookies(struct pfctl *, uint8_t, struct pfctl_watermarks *);
+
+int parse_config(char *, struct pfctl *);
+int parse_flags(char *);
+int pfctl_load_anchors(int, struct pfctl *);
+
+void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, int);
+void print_src_node(struct pfctl_src_node *, int);
+void print_eth_rule(struct pfctl_eth_rule *, const char *, int);
+void print_rule(struct pfctl_rule *, const char *, int, int);
+void print_tabledef(const char *, int, int, struct node_tinithead *);
+void print_status(struct pfctl_status *, struct pfctl_syncookies *, int);
+void print_running(struct pfctl_status *);
+
+int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
+ struct node_queue_opt *);
+int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *,
+ struct node_queue_opt *);
+
+void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *,
+ struct node_queue_opt *);
+void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *,
+ int, struct node_queue_opt *);
+
+int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *,
+ u_int32_t, struct pfr_uktable *);
+
+void pfctl_clear_fingerprints(int, int);
+int pfctl_file_fingerprints(int, int, const char *);
+pf_osfp_t pfctl_get_fingerprint(const char *);
+int pfctl_load_fingerprints(int, int);
+char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t);
+void pfctl_show_fingerprints(int);
+
+
+struct icmptypeent {
+ const char *name;
+ u_int8_t type;
+};
+
+struct icmpcodeent {
+ const char *name;
+ u_int8_t type;
+ u_int8_t code;
+};
+
+const struct icmptypeent *geticmptypebynumber(u_int8_t, sa_family_t);
+const struct icmptypeent *geticmptypebyname(char *, sa_family_t);
+const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, sa_family_t);
+const struct icmpcodeent *geticmpcodebyname(u_long, char *, sa_family_t);
+
+struct pf_timeout {
+ const char *name;
+ int timeout;
+};
+
+#define PFCTL_FLAG_FILTER 0x02
+#define PFCTL_FLAG_NAT 0x04
+#define PFCTL_FLAG_OPTION 0x08
+#define PFCTL_FLAG_ALTQ 0x10
+#define PFCTL_FLAG_TABLE 0x20
+#define PFCTL_FLAG_ETH 0x40
+
+extern const struct pf_timeout pf_timeouts[];
+
+void set_ipmask(struct node_host *, int);
+int check_netmask(struct node_host *, sa_family_t);
+int unmask(struct pf_addr *);
+struct node_host *gen_dynnode(struct node_host *, sa_family_t);
+void ifa_load(void);
+unsigned int ifa_nametoindex(const char *);
+char *ifa_indextoname(unsigned int, char *);
+int get_query_socket(void);
+struct node_host *ifa_exists(char *);
+struct node_host *ifa_grouplookup(char *ifa_name, int flags);
+struct node_host *ifa_lookup(char *, int);
+struct node_host *host(const char *, int);
+
+int append_addr(struct pfr_buffer *, char *, int, int);
+int append_addr_host(struct pfr_buffer *,
+ struct node_host *, int, int);
+int pfr_ktable_compare(struct pfr_ktable *,
+ struct pfr_ktable *);
+RB_PROTOTYPE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare);
+
+#endif /* _PFCTL_PARSER_H_ */
diff --git a/sbin/pfctl/pfctl_qstats.c b/sbin/pfctl/pfctl_qstats.c
new file mode 100644
index 000000000000..397598b0c114
--- /dev/null
+++ b/sbin/pfctl/pfctl_qstats.c
@@ -0,0 +1,513 @@
+/* $OpenBSD: pfctl_qstats.c,v 1.30 2004/04/27 21:47:32 kjc Exp $ */
+
+/*
+ * Copyright (c) Henning Brauer <henning@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/cdefs.h>
+#define PFIOC_USE_LATEST
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <netinet/in.h>
+#include <net/pfvar.h>
+#include <arpa/inet.h>
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <net/altq/altq.h>
+#include <net/altq/altq_cbq.h>
+#include <net/altq/altq_codel.h>
+#include <net/altq/altq_priq.h>
+#include <net/altq/altq_hfsc.h>
+#include <net/altq/altq_fairq.h>
+
+#include "pfctl.h"
+#include "pfctl_parser.h"
+
+union class_stats {
+ class_stats_t cbq_stats;
+ struct priq_classstats priq_stats;
+ struct hfsc_classstats hfsc_stats;
+ struct fairq_classstats fairq_stats;
+ struct codel_ifstats codel_stats;
+};
+
+#define AVGN_MAX 8
+#define STAT_INTERVAL 5
+
+struct queue_stats {
+ union class_stats data;
+ int avgn;
+ double avg_bytes;
+ double avg_packets;
+ u_int64_t prev_bytes;
+ u_int64_t prev_packets;
+};
+
+struct pf_altq_node {
+ struct pf_altq altq;
+ struct pf_altq_node *next;
+ struct pf_altq_node *children;
+ struct queue_stats qstats;
+};
+
+int pfctl_update_qstats(int, struct pf_altq_node **);
+void pfctl_insert_altq_node(struct pf_altq_node **,
+ const struct pf_altq, const struct queue_stats);
+struct pf_altq_node *pfctl_find_altq_node(struct pf_altq_node *,
+ const char *, const char *);
+void pfctl_print_altq_node(int, const struct pf_altq_node *,
+ unsigned, int);
+void print_cbqstats(struct queue_stats);
+void print_codelstats(struct queue_stats);
+void print_priqstats(struct queue_stats);
+void print_hfscstats(struct queue_stats);
+void print_fairqstats(struct queue_stats);
+void pfctl_free_altq_node(struct pf_altq_node *);
+void pfctl_print_altq_nodestat(int,
+ const struct pf_altq_node *);
+
+void update_avg(struct pf_altq_node *);
+
+int
+pfctl_show_altq(int dev, const char *iface, int opts, int verbose2)
+{
+ struct pf_altq_node *root = NULL, *node;
+ int nodes, dotitle = (opts & PF_OPT_SHOWALL);
+
+#ifdef __FreeBSD__
+ if (!altqsupport)
+ return (-1);
+#endif
+
+ if ((nodes = pfctl_update_qstats(dev, &root)) < 0)
+ return (-1);
+
+ if (nodes == 0)
+ printf("No queue in use\n");
+ for (node = root; node != NULL; node = node->next) {
+ if (iface != NULL && strcmp(node->altq.ifname, iface))
+ continue;
+ if (dotitle) {
+ pfctl_print_title("ALTQ:");
+ dotitle = 0;
+ }
+ pfctl_print_altq_node(dev, node, 0, opts);
+ }
+
+ while (verbose2 && nodes > 0) {
+ printf("\n");
+ fflush(stdout);
+ sleep(STAT_INTERVAL);
+ if ((nodes = pfctl_update_qstats(dev, &root)) == -1)
+ return (-1);
+ for (node = root; node != NULL; node = node->next) {
+ if (iface != NULL && strcmp(node->altq.ifname, iface))
+ continue;
+#ifdef __FreeBSD__
+ if (node->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
+ continue;
+#endif
+ pfctl_print_altq_node(dev, node, 0, opts);
+ }
+ }
+ pfctl_free_altq_node(root);
+ return (0);
+}
+
+int
+pfctl_update_qstats(int dev, struct pf_altq_node **root)
+{
+ struct pf_altq_node *node;
+ struct pfioc_altq pa;
+ struct pfioc_qstats pq;
+ u_int32_t mnr, nr;
+ struct queue_stats qstats;
+ static u_int32_t last_ticket;
+
+ memset(&pa, 0, sizeof(pa));
+ memset(&pq, 0, sizeof(pq));
+ memset(&qstats, 0, sizeof(qstats));
+ pa.version = PFIOC_ALTQ_VERSION;
+ if (ioctl(dev, DIOCGETALTQS, &pa)) {
+ warn("DIOCGETALTQS");
+ return (-1);
+ }
+
+ /* if a new set is found, start over */
+ if (pa.ticket != last_ticket && *root != NULL) {
+ pfctl_free_altq_node(*root);
+ *root = NULL;
+ }
+ last_ticket = pa.ticket;
+
+ mnr = pa.nr;
+ for (nr = 0; nr < mnr; ++nr) {
+ pa.nr = nr;
+ if (ioctl(dev, DIOCGETALTQ, &pa)) {
+ warn("DIOCGETALTQ");
+ return (-1);
+ }
+#ifdef __FreeBSD__
+ if ((pa.altq.qid > 0 || pa.altq.scheduler == ALTQT_CODEL) &&
+ !(pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED)) {
+#else
+ if (pa.altq.qid > 0) {
+#endif
+ pq.nr = nr;
+ pq.ticket = pa.ticket;
+ pq.buf = &qstats.data;
+ pq.nbytes = sizeof(qstats.data);
+ pq.version = altq_stats_version(pa.altq.scheduler);
+ if (ioctl(dev, DIOCGETQSTATS, &pq)) {
+ warn("DIOCGETQSTATS");
+ return (-1);
+ }
+ if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
+ pa.altq.ifname)) != NULL) {
+ memcpy(&node->qstats.data, &qstats.data,
+ sizeof(qstats.data));
+ update_avg(node);
+ } else {
+ pfctl_insert_altq_node(root, pa.altq, qstats);
+ }
+ }
+#ifdef __FreeBSD__
+ else if (pa.altq.local_flags & PFALTQ_FLAG_IF_REMOVED) {
+ memset(&qstats.data, 0, sizeof(qstats.data));
+ if ((node = pfctl_find_altq_node(*root, pa.altq.qname,
+ pa.altq.ifname)) != NULL) {
+ memcpy(&node->qstats.data, &qstats.data,
+ sizeof(qstats.data));
+ update_avg(node);
+ } else {
+ pfctl_insert_altq_node(root, pa.altq, qstats);
+ }
+ }
+#endif
+ }
+ return (mnr);
+}
+
+void
+pfctl_insert_altq_node(struct pf_altq_node **root,
+ const struct pf_altq altq, const struct queue_stats qstats)
+{
+ struct pf_altq_node *node;
+
+ node = calloc(1, sizeof(struct pf_altq_node));
+ if (node == NULL)
+ err(1, "pfctl_insert_altq_node: calloc");
+ memcpy(&node->altq, &altq, sizeof(struct pf_altq));
+ memcpy(&node->qstats, &qstats, sizeof(qstats));
+ node->next = node->children = NULL;
+
+ if (*root == NULL)
+ *root = node;
+ else if (!altq.parent[0]) {
+ struct pf_altq_node *prev = *root;
+
+ while (prev->next != NULL)
+ prev = prev->next;
+ prev->next = node;
+ } else {
+ struct pf_altq_node *parent;
+
+ parent = pfctl_find_altq_node(*root, altq.parent, altq.ifname);
+ if (parent == NULL)
+ errx(1, "parent %s not found", altq.parent);
+ if (parent->children == NULL)
+ parent->children = node;
+ else {
+ struct pf_altq_node *prev = parent->children;
+
+ while (prev->next != NULL)
+ prev = prev->next;
+ prev->next = node;
+ }
+ }
+ update_avg(node);
+}
+
+struct pf_altq_node *
+pfctl_find_altq_node(struct pf_altq_node *root, const char *qname,
+ const char *ifname)
+{
+ struct pf_altq_node *node, *child;
+
+ for (node = root; node != NULL; node = node->next) {
+ if (!strcmp(node->altq.qname, qname)
+ && !(strcmp(node->altq.ifname, ifname)))
+ return (node);
+ if (node->children != NULL) {
+ child = pfctl_find_altq_node(node->children, qname,
+ ifname);
+ if (child != NULL)
+ return (child);
+ }
+ }
+ return (NULL);
+}
+
+void
+pfctl_print_altq_node(int dev, const struct pf_altq_node *node,
+ unsigned int level, int opts)
+{
+ const struct pf_altq_node *child;
+
+ if (node == NULL)
+ return;
+
+ print_altq(&node->altq, level, NULL, NULL);
+
+ if (node->children != NULL) {
+ printf("{");
+ for (child = node->children; child != NULL;
+ child = child->next) {
+ printf("%s", child->altq.qname);
+ if (child->next != NULL)
+ printf(", ");
+ }
+ printf("}");
+ }
+ printf("\n");
+
+ if (opts & PF_OPT_VERBOSE)
+ pfctl_print_altq_nodestat(dev, node);
+
+ if (opts & PF_OPT_DEBUG)
+ printf(" [ qid=%u ifname=%s ifbandwidth=%s ]\n",
+ node->altq.qid, node->altq.ifname,
+ rate2str((double)(node->altq.ifbandwidth)));
+
+ for (child = node->children; child != NULL;
+ child = child->next)
+ pfctl_print_altq_node(dev, child, level + 1, opts);
+}
+
+void
+pfctl_print_altq_nodestat(int dev, const struct pf_altq_node *a)
+{
+ if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL)
+ return;
+
+#ifdef __FreeBSD__
+ if (a->altq.local_flags & PFALTQ_FLAG_IF_REMOVED)
+ return;
+#endif
+ switch (a->altq.scheduler) {
+ case ALTQT_CBQ:
+ print_cbqstats(a->qstats);
+ break;
+ case ALTQT_PRIQ:
+ print_priqstats(a->qstats);
+ break;
+ case ALTQT_HFSC:
+ print_hfscstats(a->qstats);
+ break;
+ case ALTQT_FAIRQ:
+ print_fairqstats(a->qstats);
+ break;
+ case ALTQT_CODEL:
+ print_codelstats(a->qstats);
+ break;
+ }
+}
+
+void
+print_cbqstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.cbq_stats.xmit_cnt.packets,
+ (unsigned long long)cur.data.cbq_stats.xmit_cnt.bytes,
+ (unsigned long long)cur.data.cbq_stats.drop_cnt.packets,
+ (unsigned long long)cur.data.cbq_stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d borrows: %6u suspends: %6u ]\n",
+ cur.data.cbq_stats.qcnt, cur.data.cbq_stats.qmax,
+ cur.data.cbq_stats.borrows, cur.data.cbq_stats.delays);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_codelstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.codel_stats.cl_xmitcnt.packets,
+ (unsigned long long)cur.data.codel_stats.cl_xmitcnt.bytes,
+ (unsigned long long)cur.data.codel_stats.cl_dropcnt.packets +
+ cur.data.codel_stats.stats.drop_cnt.packets,
+ (unsigned long long)cur.data.codel_stats.cl_dropcnt.bytes +
+ cur.data.codel_stats.stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.codel_stats.qlength, cur.data.codel_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_priqstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.priq_stats.xmitcnt.packets,
+ (unsigned long long)cur.data.priq_stats.xmitcnt.bytes,
+ (unsigned long long)cur.data.priq_stats.dropcnt.packets,
+ (unsigned long long)cur.data.priq_stats.dropcnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.priq_stats.qlength, cur.data.priq_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_hfscstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.hfsc_stats.xmit_cnt.packets,
+ (unsigned long long)cur.data.hfsc_stats.xmit_cnt.bytes,
+ (unsigned long long)cur.data.hfsc_stats.drop_cnt.packets,
+ (unsigned long long)cur.data.hfsc_stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.hfsc_stats.qlength, cur.data.hfsc_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+print_fairqstats(struct queue_stats cur)
+{
+ printf(" [ pkts: %10llu bytes: %10llu "
+ "dropped pkts: %6llu bytes: %6llu ]\n",
+ (unsigned long long)cur.data.fairq_stats.xmit_cnt.packets,
+ (unsigned long long)cur.data.fairq_stats.xmit_cnt.bytes,
+ (unsigned long long)cur.data.fairq_stats.drop_cnt.packets,
+ (unsigned long long)cur.data.fairq_stats.drop_cnt.bytes);
+ printf(" [ qlength: %3d/%3d ]\n",
+ cur.data.fairq_stats.qlength, cur.data.fairq_stats.qlimit);
+
+ if (cur.avgn < 2)
+ return;
+
+ printf(" [ measured: %7.1f packets/s, %s/s ]\n",
+ cur.avg_packets / STAT_INTERVAL,
+ rate2str((8 * cur.avg_bytes) / STAT_INTERVAL));
+}
+
+void
+pfctl_free_altq_node(struct pf_altq_node *node)
+{
+ while (node != NULL) {
+ struct pf_altq_node *prev;
+
+ if (node->children != NULL)
+ pfctl_free_altq_node(node->children);
+ prev = node;
+ node = node->next;
+ free(prev);
+ }
+}
+
+void
+update_avg(struct pf_altq_node *a)
+{
+ struct queue_stats *qs;
+ u_int64_t b, p;
+ int n;
+
+ if (a->altq.qid == 0 && a->altq.scheduler != ALTQT_CODEL)
+ return;
+
+ qs = &a->qstats;
+ n = qs->avgn;
+
+ switch (a->altq.scheduler) {
+ case ALTQT_CBQ:
+ b = qs->data.cbq_stats.xmit_cnt.bytes;
+ p = qs->data.cbq_stats.xmit_cnt.packets;
+ break;
+ case ALTQT_PRIQ:
+ b = qs->data.priq_stats.xmitcnt.bytes;
+ p = qs->data.priq_stats.xmitcnt.packets;
+ break;
+ case ALTQT_HFSC:
+ b = qs->data.hfsc_stats.xmit_cnt.bytes;
+ p = qs->data.hfsc_stats.xmit_cnt.packets;
+ break;
+ case ALTQT_FAIRQ:
+ b = qs->data.fairq_stats.xmit_cnt.bytes;
+ p = qs->data.fairq_stats.xmit_cnt.packets;
+ break;
+ case ALTQT_CODEL:
+ b = qs->data.codel_stats.cl_xmitcnt.bytes;
+ p = qs->data.codel_stats.cl_xmitcnt.packets;
+ break;
+ default:
+ b = 0;
+ p = 0;
+ break;
+ }
+
+ if (n == 0) {
+ qs->prev_bytes = b;
+ qs->prev_packets = p;
+ qs->avgn++;
+ return;
+ }
+
+ if (b >= qs->prev_bytes)
+ qs->avg_bytes = ((qs->avg_bytes * (n - 1)) +
+ (b - qs->prev_bytes)) / n;
+
+ if (p >= qs->prev_packets)
+ qs->avg_packets = ((qs->avg_packets * (n - 1)) +
+ (p - qs->prev_packets)) / n;
+
+ qs->prev_bytes = b;
+ qs->prev_packets = p;
+ if (n < AVGN_MAX)
+ qs->avgn++;
+}
diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c
new file mode 100644
index 000000000000..3b7161420e33
--- /dev/null
+++ b/sbin/pfctl/pfctl_radix.c
@@ -0,0 +1,481 @@
+/* $OpenBSD: pfctl_radix.c,v 1.27 2005/05/21 21:03:58 henning Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002 Cedric Berger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <err.h>
+
+#include "pfctl.h"
+#include "pfctl_parser.h"
+
+#define BUF_SIZE 256
+
+extern int dev;
+
+static int pfr_next_token(char buf[BUF_SIZE], FILE *);
+
+struct pfr_ktablehead pfr_ktables = { 0 };
+RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare);
+
+int
+pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q)
+{
+ int d;
+
+ if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE)))
+ return (d);
+ return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor));
+}
+
+static void
+pfr_report_error(struct pfr_table *tbl, struct pfioc_table *io,
+ const char *err)
+{
+ unsigned long maxcount;
+ size_t s;
+
+ s = sizeof(maxcount);
+ if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, NULL,
+ 0) == -1)
+ return;
+
+ if (io->pfrio_size > maxcount || io->pfrio_size2 > maxcount)
+ fprintf(stderr, "cannot %s %s: too many elements.\n"
+ "Consider increasing net.pf.request_maxcount.",
+ err, tbl->pfrt_name);
+}
+
+int
+pfr_add_table(struct pfr_table *tbl, int *nadd, int flags)
+{
+ return (pfctl_add_table(pfh, tbl, nadd, flags));
+}
+
+int
+pfr_del_table(struct pfr_table *tbl, int *ndel, int flags)
+{
+ return (pfctl_del_table(pfh, tbl, ndel, flags));
+}
+
+int
+pfr_get_tables(struct pfr_table *filter, struct pfr_table *tbl, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (size == NULL || *size < 0 || (*size && tbl == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ if (filter != NULL)
+ io.pfrio_table = *filter;
+ io.pfrio_buffer = tbl;
+ io.pfrio_esize = sizeof(*tbl);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETTABLES, &io)) {
+ pfr_report_error(tbl, &io, "get table");
+ return (-1);
+ }
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_clr_addrs(struct pfr_table *tbl, int *ndel, int flags)
+{
+ return (pfctl_clear_addrs(pfh, tbl, ndel, flags));
+}
+
+int
+pfr_add_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nadd, int flags)
+{
+ int ret;
+
+ if (*nadd)
+ *nadd = 0;
+
+ ret = pfctl_table_add_addrs_h(pfh, tbl, addr, size, nadd, flags);
+ if (ret) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pfr_del_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *ndel, int flags)
+{
+ int ret;
+
+ ret = pfctl_table_del_addrs_h(pfh, tbl, addr, size, ndel, flags);
+ if (ret) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pfr_set_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nadd, int *ndel, int *nchange, int flags)
+{
+ int ret;
+
+ ret = pfctl_table_set_addrs_h(pfh, tbl, addr, size, nadd, ndel,
+ nchange, flags);
+ if (ret) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pfr_get_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int *size,
+ int flags)
+{
+ int ret;
+
+ ret = pfctl_table_get_addrs(dev, tbl, addr, size, flags);
+ if (ret) {
+ errno = ret;
+ return (-1);
+ }
+ return (0);
+}
+
+int
+pfr_get_astats(struct pfr_table *tbl, struct pfr_astats *addr, int *size,
+ int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size == NULL || *size < 0 ||
+ (*size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = *size;
+ if (ioctl(dev, DIOCRGETASTATS, &io)) {
+ pfr_report_error(tbl, &io, "get astats from");
+ return (-1);
+ }
+ *size = io.pfrio_size;
+ return (0);
+}
+
+int
+pfr_clr_astats(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nzero, int flags)
+{
+ struct pfioc_table io;
+
+ if (size < 0 || !tbl || (size && !addr)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRCLRASTATS, &io) == -1)
+ return (-1);
+ if (nzero)
+ *nzero = io.pfrio_nzero;
+ return (0);
+}
+
+int
+pfr_tst_addrs(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nmatch, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ if (ioctl(dev, DIOCRTSTADDRS, &io)) {
+ pfr_report_error(tbl, &io, "test addresses in");
+ return (-1);
+ }
+ if (nmatch)
+ *nmatch = io.pfrio_nmatch;
+ return (0);
+}
+
+int
+pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size,
+ int *nadd, int *naddr, int ticket, int flags)
+{
+ struct pfioc_table io;
+
+ if (tbl == NULL || size < 0 || (size && addr == NULL)) {
+ DBGPRINT("%s %p %d %p\n", __func__, tbl, size, addr);
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ io.pfrio_flags = flags;
+ io.pfrio_table = *tbl;
+ io.pfrio_buffer = addr;
+ io.pfrio_esize = sizeof(*addr);
+ io.pfrio_size = size;
+ io.pfrio_ticket = ticket;
+ if (ioctl(dev, DIOCRINADEFINE, &io)) {
+ pfr_report_error(tbl, &io, "define inactive set table");
+ return (-1);
+ }
+ if (nadd != NULL)
+ *nadd = io.pfrio_nadd;
+ if (naddr != NULL)
+ *naddr = io.pfrio_naddr;
+ return (0);
+}
+
+/* interface management code */
+
+int
+pfi_get_ifaces(const char *filter, struct pfi_kif *buf, int *size)
+{
+ struct pfioc_iface io;
+
+ if (size == NULL || *size < 0 || (*size && buf == NULL)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bzero(&io, sizeof io);
+ if (filter != NULL)
+ if (strlcpy(io.pfiio_name, filter, sizeof(io.pfiio_name)) >=
+ sizeof(io.pfiio_name)) {
+ errno = EINVAL;
+ return (-1);
+ }
+ io.pfiio_buffer = buf;
+ io.pfiio_esize = sizeof(*buf);
+ io.pfiio_size = *size;
+ if (ioctl(dev, DIOCIGETIFACES, &io))
+ return (-1);
+ *size = io.pfiio_size;
+ return (0);
+}
+
+/* buffer management code */
+
+const size_t buf_esize[PFRB_MAX] = { 0,
+ sizeof(struct pfr_table), sizeof(struct pfr_tstats),
+ sizeof(struct pfr_addr), sizeof(struct pfr_astats),
+ sizeof(struct pfi_kif), sizeof(struct pfioc_trans_e)
+};
+
+/*
+ * add one element to the buffer
+ */
+int
+pfr_buf_add(struct pfr_buffer *b, const void *e)
+{
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX ||
+ e == NULL) {
+ errno = EINVAL;
+ return (-1);
+ }
+ bs = buf_esize[b->pfrb_type];
+ if (b->pfrb_size == b->pfrb_msize)
+ if (pfr_buf_grow(b, 0))
+ return (-1);
+ memcpy(((caddr_t)b->pfrb_caddr) + bs * b->pfrb_size, e, bs);
+ b->pfrb_size++;
+ return (0);
+}
+
+/*
+ * return next element of the buffer (or first one if prev is NULL)
+ * see PFRB_FOREACH macro
+ */
+void *
+pfr_buf_next(struct pfr_buffer *b, const void *prev)
+{
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX)
+ return (NULL);
+ if (b->pfrb_size == 0)
+ return (NULL);
+ if (prev == NULL)
+ return (b->pfrb_caddr);
+ bs = buf_esize[b->pfrb_type];
+ if ((((caddr_t)prev)-((caddr_t)b->pfrb_caddr)) / bs >= b->pfrb_size-1)
+ return (NULL);
+ return (((caddr_t)prev) + bs);
+}
+
+/*
+ * minsize:
+ * 0: make the buffer somewhat bigger
+ * n: make room for "n" entries in the buffer
+ */
+int
+pfr_buf_grow(struct pfr_buffer *b, int minsize)
+{
+ caddr_t p;
+ size_t bs;
+
+ if (b == NULL || b->pfrb_type <= 0 || b->pfrb_type >= PFRB_MAX) {
+ errno = EINVAL;
+ return (-1);
+ }
+ if (minsize != 0 && minsize <= b->pfrb_msize)
+ return (0);
+ bs = buf_esize[b->pfrb_type];
+ if (!b->pfrb_msize) {
+ if (minsize < 64)
+ minsize = 64;
+ }
+ if (minsize == 0)
+ minsize = b->pfrb_msize * 2;
+ p = reallocarray(b->pfrb_caddr, minsize, bs);
+ if (p == NULL)
+ return (-1);
+ bzero(p + b->pfrb_msize * bs, (minsize - b->pfrb_msize) * bs);
+ b->pfrb_caddr = p;
+ b->pfrb_msize = minsize;
+ return (0);
+}
+
+/*
+ * reset buffer and free memory.
+ */
+void
+pfr_buf_clear(struct pfr_buffer *b)
+{
+ if (b == NULL)
+ return;
+ free(b->pfrb_caddr);
+ b->pfrb_caddr = NULL;
+ b->pfrb_size = b->pfrb_msize = 0;
+}
+
+int
+pfr_buf_load(struct pfr_buffer *b, char *file, int nonetwork,
+ int (*append_addr)(struct pfr_buffer *, char *, int, int), int opts)
+{
+ FILE *fp;
+ char buf[BUF_SIZE];
+ int rv;
+
+ if (file == NULL)
+ return (0);
+ if (!strcmp(file, "-"))
+ fp = stdin;
+ else {
+ fp = pfctl_fopen(file, "r");
+ if (fp == NULL)
+ return (-1);
+ }
+ while ((rv = pfr_next_token(buf, fp)) == 1)
+ if (append_addr(b, buf, nonetwork, opts)) {
+ rv = -1;
+ break;
+ }
+ if (fp != stdin)
+ fclose(fp);
+ return (rv);
+}
+
+int
+pfr_next_token(char buf[BUF_SIZE], FILE *fp)
+{
+ static char next_ch = ' ';
+ int i = 0;
+
+ for (;;) {
+ /* skip spaces */
+ while (isspace(next_ch) && !feof(fp))
+ next_ch = fgetc(fp);
+ /* remove from '#' or ';' until end of line */
+ if (next_ch == '#' || next_ch == ';')
+ while (!feof(fp)) {
+ next_ch = fgetc(fp);
+ if (next_ch == '\n')
+ break;
+ }
+ else
+ break;
+ }
+ if (feof(fp)) {
+ next_ch = ' ';
+ return (0);
+ }
+ do {
+ if (i < BUF_SIZE)
+ buf[i++] = next_ch;
+ next_ch = fgetc(fp);
+ } while (!feof(fp) && !isspace(next_ch));
+ if (i >= BUF_SIZE) {
+ errno = EINVAL;
+ return (-1);
+ }
+ buf[i] = '\0';
+ return (1);
+}
diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c
new file mode 100644
index 000000000000..aae347712547
--- /dev/null
+++ b/sbin/pfctl/pfctl_table.c
@@ -0,0 +1,716 @@
+/* $OpenBSD: pfctl_table.c,v 1.67 2008/06/10 20:55:02 mcbride Exp $ */
+
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2002 Cedric Berger
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <net/pfvar.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "pfctl_parser.h"
+#include "pfctl.h"
+
+extern void usage(void);
+static void print_table(const struct pfr_table *, int, int);
+static int print_tstats(const struct pfr_tstats *, int);
+static int load_addr(struct pfr_buffer *, int, char *[], char *, int, int);
+static void print_addrx(struct pfr_addr *, struct pfr_addr *, int);
+static int nonzero_astats(struct pfr_astats *);
+static void print_astats(struct pfr_astats *, int);
+static void xprintf(int, const char *, ...);
+static void print_iface(struct pfi_kif *, int);
+
+static const char *stats_text[PFR_DIR_MAX][PFR_OP_TABLE_MAX] = {
+ { "In/Block:", "In/Pass:", "In/XPass:" },
+ { "Out/Block:", "Out/Pass:", "Out/XPass:" }
+};
+
+static const char *istats_text[2][2][2] = {
+ { { "In4/Pass:", "In4/Block:" }, { "Out4/Pass:", "Out4/Block:" } },
+ { { "In6/Pass:", "In6/Block:" }, { "Out6/Pass:", "Out6/Block:" } }
+};
+
+#define RVTEST(fct) do { \
+ if ((!(opts & PF_OPT_NOACTION) || \
+ (opts & PF_OPT_DUMMYACTION)) && \
+ (fct)) { \
+ if ((opts & PF_OPT_RECURSE) == 0) \
+ warnx("%s", pf_strerror(errno)); \
+ goto _error; \
+ } \
+ } while (0)
+
+#define CREATE_TABLE do { \
+ warn_duplicate_tables(table.pfrt_name, \
+ table.pfrt_anchor); \
+ table.pfrt_flags |= PFR_TFLAG_PERSIST; \
+ if ((!(opts & PF_OPT_NOACTION) || \
+ (opts & PF_OPT_DUMMYACTION)) && \
+ (pfr_add_table(&table, &nadd, flags)) && \
+ (errno != EPERM)) { \
+ warnx("%s", pf_strerror(errno)); \
+ goto _error; \
+ } \
+ if (nadd) { \
+ xprintf(opts, "%d table created", nadd); \
+ if (opts & PF_OPT_NOACTION) \
+ return (0); \
+ } \
+ table.pfrt_flags &= ~PFR_TFLAG_PERSIST; \
+ } while(0)
+
+int
+pfctl_do_clear_tables(const char *anchor, int opts)
+{
+ int rv;
+
+ if ((rv = pfctl_table(0, NULL, NULL, "-F", NULL, anchor, opts)) == -1) {
+ if ((opts & PF_OPT_IGNFAIL) == 0)
+ exit(1);
+ }
+
+ return (rv);
+}
+
+void
+pfctl_show_tables(const char *anchor, int opts)
+{
+ if (pfctl_table(0, NULL, NULL, "-s", NULL, anchor, opts))
+ exit(1);
+}
+
+int
+pfctl_table(int argc, char *argv[], char *tname, const char *command,
+ char *file, const char *anchor, int opts)
+{
+ struct pfr_table table;
+ struct pfr_buffer b, b2;
+ struct pfr_addr *a, *a2;
+ int nadd = 0, ndel = 0, nchange = 0, nzero = 0;
+ int rv = 0, flags = 0, nmatch = 0;
+ void *p;
+
+ if (command == NULL)
+ usage();
+ if (opts & PF_OPT_NOACTION)
+ flags |= PFR_FLAG_DUMMY;
+
+ bzero(&b, sizeof(b));
+ bzero(&b2, sizeof(b2));
+ bzero(&table, sizeof(table));
+ if (tname != NULL) {
+ if (strlen(tname) >= PF_TABLE_NAME_SIZE)
+ usage();
+ if (strlcpy(table.pfrt_name, tname,
+ sizeof(table.pfrt_name)) >= sizeof(table.pfrt_name))
+ errx(1, "pfctl_table: strlcpy");
+ }
+ if (strlcpy(table.pfrt_anchor, anchor,
+ sizeof(table.pfrt_anchor)) >= sizeof(table.pfrt_anchor))
+ errx(1, "pfctl_table: strlcpy");
+
+ if (!strcmp(command, "-F")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfctl_clear_tables(pfh, &table, &ndel, flags));
+ xprintf(opts, "%d tables deleted", ndel);
+ } else if (!strcmp(command, "-s")) {
+ b.pfrb_type = (opts & PF_OPT_VERBOSE2) ?
+ PFRB_TSTATS : PFRB_TABLES;
+ if (argc || file != NULL)
+ usage();
+
+ if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0)
+ pfctl_print_title("TABLES:");
+
+ if (opts & PF_OPT_VERBOSE2) {
+ uintptr_t arg = opts & PF_OPT_DEBUG;
+ pfctl_get_tstats(pfh, &table,
+ (pfctl_get_tstats_fn)print_tstats, (void *)arg);
+ } else {
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ RVTEST(pfr_get_tables(&table,
+ b.pfrb_caddr, &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+
+ if ((opts & PF_OPT_SHOWALL) && b.pfrb_size > 0)
+ pfctl_print_title("TABLES:");
+
+ PFRB_FOREACH(p, &b)
+ print_table(p, opts & PF_OPT_VERBOSE,
+ opts & PF_OPT_DEBUG);
+ }
+ } else if (!strcmp(command, "kill")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfr_del_table(&table, &ndel, flags));
+ xprintf(opts, "%d table deleted", ndel);
+ } else if (!strcmp(command, "flush")) {
+ if (argc || file != NULL)
+ usage();
+ RVTEST(pfr_clr_addrs(&table, &ndel, flags));
+ xprintf(opts, "%d addresses deleted", ndel);
+ } else if (!strcmp(command, "add")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0, opts))
+ goto _error;
+ CREATE_TABLE;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_add_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &nadd, flags));
+ xprintf(opts, "%d/%d addresses added", nadd, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) ||
+ a->pfra_fback != PFR_FB_NONE)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "delete")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0, opts))
+ goto _error;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_del_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &ndel, flags));
+ xprintf(opts, "%d/%d addresses deleted", ndel, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) ||
+ a->pfra_fback != PFR_FB_NONE)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "replace")) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0, opts))
+ goto _error;
+ CREATE_TABLE;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_set_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &nadd, &ndel, &nchange, flags));
+ if (nadd)
+ xprintf(opts, "%d addresses added", nadd);
+ if (ndel)
+ xprintf(opts, "%d addresses deleted", ndel);
+ if (nchange)
+ xprintf(opts, "%d addresses changed", nchange);
+ if (!nadd && !ndel && !nchange)
+ xprintf(opts, "no changes");
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if ((opts & PF_OPT_VERBOSE2) ||
+ a->pfra_fback != PFR_FB_NONE)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "expire")) {
+ const char *errstr;
+ u_int lifetime;
+
+ b.pfrb_type = PFRB_ASTATS;
+ b2.pfrb_type = PFRB_ADDRS;
+ if (argc != 1 || file != NULL)
+ usage();
+ lifetime = strtonum(*argv, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "expiry time: %s", errstr);
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ RVTEST(pfr_get_astats(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(p, &b) {
+ ((struct pfr_astats *)p)->pfras_a.pfra_fback = PFR_FB_NONE;
+ if (time(NULL) - ((struct pfr_astats *)p)->pfras_tzero >
+ lifetime)
+ if (pfr_buf_add(&b2,
+ &((struct pfr_astats *)p)->pfras_a))
+ err(1, "duplicate buffer");
+ }
+
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_del_addrs(&table, b2.pfrb_caddr, b2.pfrb_size,
+ &ndel, flags));
+ xprintf(opts, "%d/%d addresses expired", ndel, b2.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b2)
+ if ((opts & PF_OPT_VERBOSE2) ||
+ a->pfra_fback != PFR_FB_NONE)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "reset")) {
+ struct pfr_astats *as;
+
+ b.pfrb_type = PFRB_ASTATS;
+ b2.pfrb_type = PFRB_ADDRS;
+ if (argc || file != NULL)
+ usage();
+ do {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ RVTEST(pfr_get_astats(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ } while (b.pfrb_size > b.pfrb_msize);
+ PFRB_FOREACH(as, &b) {
+ as->pfras_a.pfra_fback = 0;
+ if (nonzero_astats(as))
+ if (pfr_buf_add(&b2, &as->pfras_a))
+ err(1, "duplicate buffer");
+ }
+
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_clr_astats(&table, b2.pfrb_caddr, b2.pfrb_size,
+ &nzero, flags));
+ xprintf(opts, "%d/%d stats cleared", nzero, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b2)
+ if ((opts & PF_OPT_VERBOSE2) || a->pfra_fback)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "show")) {
+ b.pfrb_type = (opts & PF_OPT_VERBOSE) ?
+ PFRB_ASTATS : PFRB_ADDRS;
+ if (argc || file != NULL)
+ usage();
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (opts & PF_OPT_VERBOSE)
+ RVTEST(pfr_get_astats(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ else
+ RVTEST(pfr_get_addrs(&table, b.pfrb_caddr,
+ &b.pfrb_size, flags));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(p, &b)
+ if (opts & PF_OPT_VERBOSE)
+ print_astats(p, opts & PF_OPT_USEDNS);
+ else
+ print_addrx(p, NULL, opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "test")) {
+ b.pfrb_type = PFRB_ADDRS;
+ b2.pfrb_type = PFRB_ADDRS;
+
+ if (load_addr(&b, argc, argv, file, 1, opts))
+ goto _error;
+ if (opts & PF_OPT_VERBOSE2) {
+ flags |= PFR_FLAG_REPLACE;
+ PFRB_FOREACH(a, &b)
+ if (pfr_buf_add(&b2, a))
+ err(1, "duplicate buffer");
+ }
+ RVTEST(pfr_tst_addrs(&table, b.pfrb_caddr, b.pfrb_size,
+ &nmatch, flags));
+ xprintf(opts, "%d/%d addresses match", nmatch, b.pfrb_size);
+ if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2))
+ PFRB_FOREACH(a, &b)
+ if (a->pfra_fback == PFR_FB_MATCH)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ if (opts & PF_OPT_VERBOSE2) {
+ a2 = NULL;
+ PFRB_FOREACH(a, &b) {
+ a2 = pfr_buf_next(&b2, a2);
+ print_addrx(a2, a, opts & PF_OPT_USEDNS);
+ }
+ }
+ if (nmatch < b.pfrb_size)
+ rv = 2;
+ } else if (!strcmp(command, "zero") && (argc || file != NULL)) {
+ b.pfrb_type = PFRB_ADDRS;
+ if (load_addr(&b, argc, argv, file, 0, opts))
+ goto _error;
+ if (opts & PF_OPT_VERBOSE)
+ flags |= PFR_FLAG_FEEDBACK;
+ RVTEST(pfr_clr_astats(&table, b.pfrb_caddr, b.pfrb_size,
+ &nzero, flags));
+ xprintf(opts, "%d/%d addresses cleared", nzero, b.pfrb_size);
+ if (opts & PF_OPT_VERBOSE)
+ PFRB_FOREACH(a, &b)
+ if (opts & PF_OPT_VERBOSE2 ||
+ a->pfra_fback != PFR_FB_NONE)
+ print_addrx(a, NULL,
+ opts & PF_OPT_USEDNS);
+ } else if (!strcmp(command, "zero")) {
+ flags |= PFR_FLAG_ADDRSTOO;
+ RVTEST(pfctl_clear_tstats(pfh, &table, &nzero, flags));
+ xprintf(opts, "%d table/stats cleared", nzero);
+ } else
+ warnx("pfctl_table: unknown command '%s'", command);
+ goto _cleanup;
+
+_error:
+ rv = -1;
+_cleanup:
+ pfr_buf_clear(&b);
+ pfr_buf_clear(&b2);
+ return (rv);
+}
+
+void
+print_table(const struct pfr_table *ta, int verbose, int debug)
+{
+ if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE))
+ return;
+ if (verbose)
+ printf("%c%c%c%c%c%c%c\t",
+ (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-',
+ (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-');
+
+ printf("%s", ta->pfrt_name);
+ if (ta->pfrt_anchor[0] != '\0')
+ printf("@%s", ta->pfrt_anchor);
+
+ printf("\n");
+}
+
+int
+print_tstats(const struct pfr_tstats *ts, int debug)
+{
+ time_t time = ts->pfrts_tzero;
+ int dir, op;
+ char *ct;
+
+ if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE))
+ return (0);
+ ct = ctime(&time);
+ print_table(&ts->pfrts_t, 1, debug);
+ printf("\tAddresses: %d\n", ts->pfrts_cnt);
+ if (ct)
+ printf("\tCleared: %s", ct);
+ else
+ printf("\tCleared: %lld\n", (long long)time);
+ printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n",
+ ts->pfrts_refcnt[PFR_REFCNT_ANCHOR],
+ ts->pfrts_refcnt[PFR_REFCNT_RULE]);
+ printf("\tEvaluations: [ NoMatch: %-18llu Match: %-18llu ]\n",
+ (unsigned long long)ts->pfrts_nomatch,
+ (unsigned long long)ts->pfrts_match);
+ for (dir = 0; dir < PFR_DIR_MAX; dir++)
+ for (op = 0; op < PFR_OP_TABLE_MAX; op++)
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ stats_text[dir][op],
+ (unsigned long long)ts->pfrts_packets[dir][op],
+ (unsigned long long)ts->pfrts_bytes[dir][op]);
+
+ return (0);
+}
+
+int
+load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file,
+ int nonetwork, int opts)
+{
+ while (argc--)
+ if (append_addr(b, *argv++, nonetwork, opts)) {
+ if (errno)
+ warn("cannot decode %s", argv[-1]);
+ return (-1);
+ }
+ if (pfr_buf_load(b, file, nonetwork, append_addr, opts)) {
+ warn("cannot load %s", file);
+ return (-1);
+ }
+ return (0);
+}
+
+void
+print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns)
+{
+ char ch, buf[256] = "{error}";
+ char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' };
+ unsigned int fback, hostnet;
+
+ fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback;
+ ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?';
+ hostnet = (ad->pfra_af == AF_INET6) ? 128 : 32;
+ inet_ntop(ad->pfra_af, &ad->pfra_u, buf, sizeof(buf));
+ printf("%c %c%s", ch, (ad->pfra_not?'!':' '), buf);
+ if (ad->pfra_net < hostnet)
+ printf("/%d", ad->pfra_net);
+ if (rad != NULL && fback != PFR_FB_NONE) {
+ if (strlcpy(buf, "{error}", sizeof(buf)) >= sizeof(buf))
+ errx(1, "print_addrx: strlcpy");
+ inet_ntop(rad->pfra_af, &rad->pfra_u, buf, sizeof(buf));
+ printf("\t%c%s", (rad->pfra_not?'!':' '), buf);
+ if (rad->pfra_net < hostnet)
+ printf("/%d", rad->pfra_net);
+ }
+ if (rad != NULL && fback == PFR_FB_NONE)
+ printf("\t nomatch");
+ if (dns && ad->pfra_net == hostnet) {
+ char host[NI_MAXHOST];
+ struct sockaddr_storage ss;
+
+ strlcpy(host, "?", sizeof(host));
+ bzero(&ss, sizeof(ss));
+ ss.ss_family = ad->pfra_af;
+ if (ss.ss_family == AF_INET) {
+ struct sockaddr_in *sin = (struct sockaddr_in *)&ss;
+
+ sin->sin_len = sizeof(*sin);
+ sin->sin_addr = ad->pfra_ip4addr;
+ } else {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ss;
+
+ sin6->sin6_len = sizeof(*sin6);
+ sin6->sin6_addr = ad->pfra_ip6addr;
+ }
+ if (getnameinfo((struct sockaddr *)&ss, ss.ss_len, host,
+ sizeof(host), NULL, 0, NI_NAMEREQD) == 0)
+ printf("\t(%s)", host);
+ }
+ printf("\n");
+}
+
+int
+nonzero_astats(struct pfr_astats *as)
+{
+ uint64_t s = 0;
+
+ for (int dir = 0; dir < PFR_DIR_MAX; dir++)
+ for (int op = 0; op < PFR_OP_ADDR_MAX; op++)
+ s |= as->pfras_packets[dir][op] |
+ as->pfras_bytes[dir][op];
+
+ return (!!s);
+}
+
+void
+print_astats(struct pfr_astats *as, int dns)
+{
+ time_t time = as->pfras_tzero;
+ int dir, op;
+ char *ct;
+
+ ct = ctime(&time);
+ print_addrx(&as->pfras_a, NULL, dns);
+ if (ct)
+ printf("\tCleared: %s", ct);
+ else
+ printf("\tCleared: %lld\n", (long long)time);
+ if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT)
+ return;
+ for (dir = 0; dir < PFR_DIR_MAX; dir++)
+ for (op = 0; op < PFR_OP_ADDR_MAX; op++)
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ stats_text[dir][op],
+ (unsigned long long)as->pfras_packets[dir][op],
+ (unsigned long long)as->pfras_bytes[dir][op]);
+}
+
+int
+pfctl_define_table(char *name, int flags, int addrs, const char *anchor,
+ struct pfr_buffer *ab, u_int32_t ticket, struct pfr_uktable *ukt)
+{
+ struct pfr_table tbl_buf;
+ struct pfr_table *tbl;
+
+ if (ukt == NULL) {
+ bzero(&tbl_buf, sizeof(tbl_buf));
+ tbl = &tbl_buf;
+ } else {
+ if (ab->pfrb_size != 0) {
+ /*
+ * copy IP addresses which come with table from
+ * temporal buffer to buffer attached to table.
+ */
+ ukt->pfrukt_addrs = *ab;
+ ab->pfrb_size = 0;
+ ab->pfrb_msize = 0;
+ ab->pfrb_caddr = NULL;
+ } else
+ memset(&ukt->pfrukt_addrs, 0,
+ sizeof(struct pfr_buffer));
+
+ tbl = &ukt->pfrukt_t;
+ }
+
+ if (strlcpy(tbl->pfrt_name, name, sizeof(tbl->pfrt_name)) >=
+ sizeof(tbl->pfrt_name) ||
+ strlcpy(tbl->pfrt_anchor, anchor, sizeof(tbl->pfrt_anchor)) >=
+ sizeof(tbl->pfrt_anchor))
+ errx(1, "%s: strlcpy", __func__);
+ tbl->pfrt_flags = flags;
+ DBGPRINT("%s %s@%s [%x]\n", __func__, tbl->pfrt_name, tbl->pfrt_anchor,
+ tbl->pfrt_flags);
+
+ /*
+ * non-root anchors processed by parse.y are loaded to kernel later.
+ * Here we load tables, which are either created for root anchor
+ * or by 'pfctl -t ... -T ...' command.
+ */
+ if (ukt != NULL)
+ return (0);
+
+ return (pfr_ina_define(tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, NULL,
+ ticket, addrs ? PFR_FLAG_ADDRSTOO : 0));
+}
+
+void
+warn_duplicate_tables(const char *tablename, const char *anchorname)
+{
+ struct pfr_buffer b;
+ struct pfr_table *t;
+
+ bzero(&b, sizeof(b));
+ b.pfrb_type = PFRB_TABLES;
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (pfr_get_tables(NULL, b.pfrb_caddr,
+ &b.pfrb_size, PFR_FLAG_ALLRSETS))
+ err(1, "pfr_get_tables");
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ PFRB_FOREACH(t, &b) {
+ if (!(t->pfrt_flags & PFR_TFLAG_ACTIVE))
+ continue;
+ if (!strcmp(anchorname, t->pfrt_anchor))
+ continue;
+ if (!strcmp(tablename, t->pfrt_name))
+ warnx("warning: table <%s> already defined"
+ " in anchor \"%s\"", tablename,
+ t->pfrt_anchor[0] ? t->pfrt_anchor : "/");
+ }
+ pfr_buf_clear(&b);
+}
+
+void
+xprintf(int opts, const char *fmt, ...)
+{
+ va_list args;
+
+ if (opts & PF_OPT_QUIET)
+ return;
+
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ if (opts & PF_OPT_DUMMYACTION)
+ fprintf(stderr, " (dummy).\n");
+ else if (opts & PF_OPT_NOACTION)
+ fprintf(stderr, " (syntax only).\n");
+ else
+ fprintf(stderr, ".\n");
+}
+
+
+/* interface stuff */
+
+void
+pfctl_show_ifaces(const char *filter, int opts)
+{
+ struct pfr_buffer b;
+ struct pfi_kif *p;
+
+ bzero(&b, sizeof(b));
+ b.pfrb_type = PFRB_IFACES;
+ for (;;) {
+ pfr_buf_grow(&b, b.pfrb_size);
+ b.pfrb_size = b.pfrb_msize;
+ if (pfi_get_ifaces(filter, b.pfrb_caddr, &b.pfrb_size))
+ errx(1, "%s", pf_strerror(errno));
+ if (b.pfrb_size <= b.pfrb_msize)
+ break;
+ }
+ if (opts & PF_OPT_SHOWALL)
+ pfctl_print_title("INTERFACES:");
+ PFRB_FOREACH(p, &b)
+ print_iface(p, opts);
+}
+
+void
+print_iface(struct pfi_kif *p, int opts)
+{
+ time_t tzero = p->pfik_tzero;
+ int i, af, dir, act;
+ char *ct;
+
+ printf("%s", p->pfik_name);
+ if (opts & PF_OPT_VERBOSE) {
+ if (p->pfik_flags & PFI_IFLAG_SKIP)
+ printf(" (skip)");
+ }
+ printf("\n");
+
+ if (!(opts & PF_OPT_VERBOSE2))
+ return;
+ ct = ctime(&tzero);
+ if (ct)
+ printf("\tCleared: %s", ct);
+ else
+ printf("\tCleared: %lld\n", (long long)tzero);
+ printf("\tReferences: %-18d\n", p->pfik_rulerefs);
+ for (i = 0; i < 8; i++) {
+ af = (i>>2) & 1;
+ dir = (i>>1) &1;
+ act = i & 1;
+ printf("\t%-12s [ Packets: %-18llu Bytes: %-18llu ]\n",
+ istats_text[af][dir][act],
+ (unsigned long long)p->pfik_packets[af][dir][act],
+ (unsigned long long)p->pfik_bytes[af][dir][act]);
+ }
+}
diff --git a/sbin/pfctl/tests/Makefile b/sbin/pfctl/tests/Makefile
new file mode 100644
index 000000000000..281cd97dee78
--- /dev/null
+++ b/sbin/pfctl/tests/Makefile
@@ -0,0 +1,12 @@
+PACKAGE= tests
+
+ATF_TESTS_C= pfctl_test
+ATF_TESTS_SH= macro
+
+LIBADD+= sbuf
+SUBDIR+= files
+WARNS=6
+
+pfctl_test.o: pfctl_test_list.inc
+
+.include <bsd.test.mk>
diff --git a/sbin/pfctl/tests/Makefile.depend b/sbin/pfctl/tests/Makefile.depend
new file mode 100644
index 000000000000..a59dba5bf1aa
--- /dev/null
+++ b/sbin/pfctl/tests/Makefile.depend
@@ -0,0 +1,15 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+ lib/${CSU_DIR} \
+ lib/atf/libatf-c \
+ lib/libc \
+ lib/libcompiler_rt \
+ lib/libsbuf \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/sbin/pfctl/tests/files/Makefile b/sbin/pfctl/tests/files/Makefile
new file mode 100644
index 000000000000..fc52b1db3c30
--- /dev/null
+++ b/sbin/pfctl/tests/files/Makefile
@@ -0,0 +1,9 @@
+PACKAGE= tests
+
+TESTSDIR= ${TESTSBASE}/sbin/pfctl/files
+BINDIR= ${TESTSDIR}
+
+# We use ${.CURDIR} as workaround so that the glob patterns work.
+FILES!= echo ${.CURDIR}/pf????.in ${.CURDIR}/pf????.include ${.CURDIR}/pf????.ok ${.CURDIR}/pf????.fail
+
+.include <bsd.progs.mk>
diff --git a/sbin/pfctl/tests/files/Makefile.depend b/sbin/pfctl/tests/files/Makefile.depend
new file mode 100644
index 000000000000..11aba52f82cf
--- /dev/null
+++ b/sbin/pfctl/tests/files/Makefile.depend
@@ -0,0 +1,10 @@
+# Autogenerated - do NOT edit!
+
+DIRDEPS = \
+
+
+.include <dirdeps.mk>
+
+.if ${DEP_RELDIR} == ${_DEP_RELDIR}
+# local dependencies - needed for -jN in clean tree
+.endif
diff --git a/sbin/pfctl/tests/files/pf0001.in b/sbin/pfctl/tests/files/pf0001.in
new file mode 100644
index 000000000000..494eee3560fe
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0001.in
@@ -0,0 +1,8 @@
+pass in all
+pass in from any to any no state
+pass in proto tcp from any port <= 1024 to any label foo_bar
+pass in proto tcp from any to any port = 25
+pass in proto tcp from 10.0.0.0/8 port > 1024 to ! 10.1.2.3 port != 22
+pass in proto igmp from 10.0.0.0/8 to 10.1.1.1 allow-opts
+pass in proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \
+"$nr:$proto:$srcaddr:$srcport:$dstaddr:$dstport"
diff --git a/sbin/pfctl/tests/files/pf0001.ok b/sbin/pfctl/tests/files/pf0001.ok
new file mode 100644
index 000000000000..10fb28bb33dc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0001.ok
@@ -0,0 +1,8 @@
+pass in all flags S/SA keep state
+pass in all no state
+pass in proto tcp from any port <= 1024 to any flags S/SA keep state label "foo_bar"
+pass in proto tcp from any to any port = smtp flags S/SA keep state
+pass in inet proto tcp from 10.0.0.0/8 port > 1024 to ! 10.1.2.3 port != ssh flags S/SA keep state
+pass in inet proto igmp from 10.0.0.0/8 to 10.1.1.1 keep state allow-opts
+pass in inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "6:tcp:1.2.3.4::any:"
+pass in inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "7:tcp:1.2.3.5::any:"
diff --git a/sbin/pfctl/tests/files/pf0002.in b/sbin/pfctl/tests/files/pf0002.in
new file mode 100644
index 000000000000..bef5d9b08d1c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0002.in
@@ -0,0 +1,34 @@
+# test
+
+block out log on tun1000000 all
+block in log on tun1000000 all
+
+block return-rst out log on tun1000000 proto tcp all
+block return-rst in log on tun1000000 proto tcp all
+block return-icmp out log on tun1000000 proto udp all
+block return-icmp in log on tun1000000 proto udp all
+
+block out log quick on tun1000000 from ! 157.161.48.183 to any
+
+block in quick on tun1000000 from any to 255.255.255.255
+
+block in log quick on tun1000000 from 10.0.0.0/8 to any
+block in log quick on tun1000000 from 172.16.0.0/12 to any
+block in quick log on tun1000000 from 192.168.0.0/16 to any
+block in quick log on tun1000000 from 255.255.255.255/32 to any
+
+block in log quick from no-route to any
+
+pass out on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state
+pass in on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state
+
+pass out on tun1000000 proto udp all keep state
+
+pass in on tun1000000 proto udp from any to any port = domain keep state
+
+pass out on tun1000000 proto tcp all keep state
+
+pass in on tun1000000 proto tcp from any to any port = ssh keep state
+pass in on tun1000000 proto tcp from any to any port = smtp keep state
+pass in on tun1000000 proto tcp from any to any port = domain keep state
+pass in on tun1000000 proto tcp from any to any port = auth keep state
diff --git a/sbin/pfctl/tests/files/pf0002.ok b/sbin/pfctl/tests/files/pf0002.ok
new file mode 100644
index 000000000000..02e3099013e5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0002.ok
@@ -0,0 +1,22 @@
+block drop out log on tun1000000 all
+block drop in log on tun1000000 all
+block return-rst out log on tun1000000 proto tcp all
+block return-rst in log on tun1000000 proto tcp all
+block return-icmp(port-unr, port-unr) out log on tun1000000 proto udp all
+block return-icmp(port-unr, port-unr) in log on tun1000000 proto udp all
+block drop out log quick on tun1000000 inet from ! 157.161.48.183 to any
+block drop in quick on tun1000000 inet from any to 255.255.255.255
+block drop in log quick on tun1000000 inet from 10.0.0.0/8 to any
+block drop in log quick on tun1000000 inet from 172.16.0.0/12 to any
+block drop in log quick on tun1000000 inet from 192.168.0.0/16 to any
+block drop in log quick on tun1000000 inet from 255.255.255.255 to any
+block drop in log quick from no-route to any
+pass out on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state
+pass in on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state
+pass out on tun1000000 proto udp all keep state
+pass in on tun1000000 proto udp from any to any port = domain keep state
+pass out on tun1000000 proto tcp all flags S/SA keep state
+pass in on tun1000000 proto tcp from any to any port = ssh flags S/SA keep state
+pass in on tun1000000 proto tcp from any to any port = smtp flags S/SA keep state
+pass in on tun1000000 proto tcp from any to any port = domain flags S/SA keep state
+pass in on tun1000000 proto tcp from any to any port = auth flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0003.in b/sbin/pfctl/tests/files/pf0003.in
new file mode 100644
index 000000000000..fc82383434b9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0003.in
@@ -0,0 +1,13 @@
+pass in all
+pass in from any to any
+
+block in proto tcp from any to any flags FUPEW/FSRPAUEW
+block in proto tcp from any to any flags SF/SFRA
+block in proto tcp from any to any flags /SFRAW
+
+pass in proto { udp, icmp, tcp } from any to any flags S/SA
+pass in from any to any flags S/SA no state
+pass in from any to any flags any no state
+pass in from any to any flags any
+pass in from any to any keep state
+pass in from any to any
diff --git a/sbin/pfctl/tests/files/pf0003.ok b/sbin/pfctl/tests/files/pf0003.ok
new file mode 100644
index 000000000000..1d9432f9d6c4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0003.ok
@@ -0,0 +1,13 @@
+pass in all flags S/SA keep state
+pass in all flags S/SA keep state
+block drop in proto tcp all flags FPUEW/FSRPAUEW
+block drop in proto tcp all flags FS/FSRA
+block drop in proto tcp all flags /FSRAW
+pass in proto udp all keep state
+pass in proto icmp all keep state
+pass in proto tcp all flags S/SA keep state
+pass in all flags S/SA no state
+pass in all no state
+pass in all flags any keep state
+pass in all flags S/SA keep state
+pass in all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0004.in b/sbin/pfctl/tests/files/pf0004.in
new file mode 100644
index 000000000000..dcd6ee916b37
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0004.in
@@ -0,0 +1,16 @@
+block in all
+block in proto tcp all
+block in proto { tcp, udp } all
+
+block in from any to any
+block in from 10.0.0.0/8 to any
+block in from ! 10.0.0.0/8 to any
+block in from { 10.0.0.0/8, 172.16.0.0/12 } to any
+
+block in proto tcp from any port = ssh to any
+block in proto tcp from any port { ssh, ftp >< 2048, != 1234, >= www } \
+ to any port 1024:2048
+
+block in proto { tcp, udp } from { 10.0.0.0/8, 172.16.0.0/12 } port { ssh, ftp } \
+ to { 192.168.0.0/16, 12.34.56.78 } port { 6667, 6668, 6669:65535 }
+
diff --git a/sbin/pfctl/tests/files/pf0004.ok b/sbin/pfctl/tests/files/pf0004.ok
new file mode 100644
index 000000000000..87b71cdeff3d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0004.ok
@@ -0,0 +1,62 @@
+block drop in all
+block drop in proto tcp all
+block drop in proto tcp all
+block drop in proto udp all
+block drop in all
+block drop in inet from 10.0.0.0/8 to any
+block drop in inet from ! 10.0.0.0/8 to any
+block drop in inet from 10.0.0.0/8 to any
+block drop in inet from 172.16.0.0/12 to any
+block drop in proto tcp from any port = ssh to any
+block drop in proto tcp from any port = ssh to any port 1024:2048
+block drop in proto tcp from any port 21 >< 2048 to any port 1024:2048
+block drop in proto tcp from any port != 1234 to any port 1024:2048
+block drop in proto tcp from any port >= 80 to any port 1024:2048
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = ircd
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = ircd
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = ircd
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = ircd
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = ircd
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668
+block drop in inet proto tcp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port = 6668
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port = 6668
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port 6669:65535
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6668
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port 6669:65535
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6667
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port = 6668
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6667
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port = 6668
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 192.168.0.0/16 port 6669:65535
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6667
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port = 6668
+block drop in inet proto udp from 172.16.0.0/12 port = ssh to 12.34.56.78 port 6669:65535
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6667
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port = 6668
+block drop in inet proto udp from 172.16.0.0/12 port = ftp to 12.34.56.78 port 6669:65535
diff --git a/sbin/pfctl/tests/files/pf0005.in b/sbin/pfctl/tests/files/pf0005.in
new file mode 100644
index 000000000000..6ad7040c2ed1
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0005.in
@@ -0,0 +1,6 @@
+foo = "ssh, ftp"
+bar = "other thing"
+inside="10.0.0.0/8"
+
+block in proto udp from $inside port { echo, $foo, ident } \
+ to 12.34.56.78 port { 6667, 0x10 }
diff --git a/sbin/pfctl/tests/files/pf0005.ok b/sbin/pfctl/tests/files/pf0005.ok
new file mode 100644
index 000000000000..6158d6779126
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0005.ok
@@ -0,0 +1,11 @@
+foo = "ssh, ftp"
+bar = "other thing"
+inside = "10.0.0.0/8"
+block drop in inet proto udp from 10.0.0.0/8 port = echo to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = echo to 12.34.56.78 port = 16
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ssh to 12.34.56.78 port = 16
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = ftp to 12.34.56.78 port = 16
+block drop in inet proto udp from 10.0.0.0/8 port = auth to 12.34.56.78 port = 6667
+block drop in inet proto udp from 10.0.0.0/8 port = auth to 12.34.56.78 port = 16
diff --git a/sbin/pfctl/tests/files/pf0006.in b/sbin/pfctl/tests/files/pf0006.in
new file mode 100644
index 000000000000..180d36d85db8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0006.in
@@ -0,0 +1,3 @@
+a=b
+c=x
+a_b_c=d
diff --git a/sbin/pfctl/tests/files/pf0006.ok b/sbin/pfctl/tests/files/pf0006.ok
new file mode 100644
index 000000000000..85d1e30aa453
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0006.ok
@@ -0,0 +1,3 @@
+a = "b"
+c = "x"
+a_b_c = "d"
diff --git a/sbin/pfctl/tests/files/pf0007.in b/sbin/pfctl/tests/files/pf0007.in
new file mode 100644
index 000000000000..02514df9cddb
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0007.in
@@ -0,0 +1,34 @@
+# test modulate state
+
+block out log on tun1000000 all
+block in log on tun1000000 all
+
+block return-rst out log on tun1000000 proto tcp all
+block return-rst in log on tun1000000 proto tcp all
+block return-icmp out log on tun1000000 proto udp all
+block return-icmp in log on tun1000000 proto udp all
+
+block out log quick on tun1000000 from ! 157.161.48.183 to any
+
+block in quick on tun1000000 from any to 255.255.255.255
+
+block in log quick on tun1000000 from 10.0.0.0/8 to any
+block in log quick on tun1000000 from 172.16.0.0/12 to any
+block in log quick on tun1000000 from 192.168.0.0/16 to any
+block in log quick on tun1000000 from 255.255.255.255/32 to any
+
+pass out on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state
+pass in on tun1000000 inet proto icmp all icmp-type 8 code 0 keep state
+
+pass out on tun1000000 proto udp all keep state
+
+pass in on tun1000000 proto udp from any to any port = domain keep state
+
+pass out on tun1000000 proto tcp all modulate state
+pass in on tun1000000 proto { tcp udp icmp } all modulate state
+pass in on tun1000000 proto { udp tcp icmp } all flags S/SA synproxy state
+
+pass in on tun1000000 proto tcp from any to any port = ssh modulate state
+pass in on tun1000000 proto tcp from any to any port = smtp modulate state
+pass in on tun1000000 proto tcp from any to any port = domain modulate state
+pass in on tun1000000 proto tcp from any to any port = auth modulate state
diff --git a/sbin/pfctl/tests/files/pf0007.ok b/sbin/pfctl/tests/files/pf0007.ok
new file mode 100644
index 000000000000..357f3180e307
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0007.ok
@@ -0,0 +1,27 @@
+block drop out log on tun1000000 all
+block drop in log on tun1000000 all
+block return-rst out log on tun1000000 proto tcp all
+block return-rst in log on tun1000000 proto tcp all
+block return-icmp(port-unr, port-unr) out log on tun1000000 proto udp all
+block return-icmp(port-unr, port-unr) in log on tun1000000 proto udp all
+block drop out log quick on tun1000000 inet from ! 157.161.48.183 to any
+block drop in quick on tun1000000 inet from any to 255.255.255.255
+block drop in log quick on tun1000000 inet from 10.0.0.0/8 to any
+block drop in log quick on tun1000000 inet from 172.16.0.0/12 to any
+block drop in log quick on tun1000000 inet from 192.168.0.0/16 to any
+block drop in log quick on tun1000000 inet from 255.255.255.255 to any
+pass out on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state
+pass in on tun1000000 inet proto icmp all icmp-type echoreq code 0 keep state
+pass out on tun1000000 proto udp all keep state
+pass in on tun1000000 proto udp from any to any port = domain keep state
+pass out on tun1000000 proto tcp all flags S/SA modulate state
+pass in on tun1000000 proto tcp all flags S/SA modulate state
+pass in on tun1000000 proto udp all keep state
+pass in on tun1000000 proto icmp all keep state
+pass in on tun1000000 proto udp all keep state
+pass in on tun1000000 proto tcp all flags S/SA synproxy state
+pass in on tun1000000 proto icmp all keep state
+pass in on tun1000000 proto tcp from any to any port = ssh flags S/SA modulate state
+pass in on tun1000000 proto tcp from any to any port = smtp flags S/SA modulate state
+pass in on tun1000000 proto tcp from any to any port = domain flags S/SA modulate state
+pass in on tun1000000 proto tcp from any to any port = auth flags S/SA modulate state
diff --git a/sbin/pfctl/tests/files/pf0008.in b/sbin/pfctl/tests/files/pf0008.in
new file mode 100644
index 000000000000..e092bd955afb
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0008.in
@@ -0,0 +1,2 @@
+extern = "{ ! 10.0.0.0/8, 10.1.2.3 }"
+block out log on tun1000001 from $extern to any
diff --git a/sbin/pfctl/tests/files/pf0008.ok b/sbin/pfctl/tests/files/pf0008.ok
new file mode 100644
index 000000000000..c8786e384cc7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0008.ok
@@ -0,0 +1,3 @@
+extern = "{ ! 10.0.0.0/8, 10.1.2.3 }"
+block drop out log on tun1000001 inet from ! 10.0.0.0/8 to any
+block drop out log on tun1000001 inet from 10.1.2.3 to any
diff --git a/sbin/pfctl/tests/files/pf0009.in b/sbin/pfctl/tests/files/pf0009.in
new file mode 100644
index 000000000000..2e4e724dbb84
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0009.in
@@ -0,0 +1,3 @@
+interfaces = "{ enc0, tun1000000 }"
+
+block in on $interfaces all
diff --git a/sbin/pfctl/tests/files/pf0009.ok b/sbin/pfctl/tests/files/pf0009.ok
new file mode 100644
index 000000000000..c7e9547a8fd3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0009.ok
@@ -0,0 +1,3 @@
+interfaces = "{ enc0, tun1000000 }"
+block drop in on enc0 all
+block drop in on tun1000000 all
diff --git a/sbin/pfctl/tests/files/pf0010.in b/sbin/pfctl/tests/files/pf0010.in
new file mode 100644
index 000000000000..250576b9961f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0010.in
@@ -0,0 +1,31 @@
+# return variants
+pass in inet proto icmp all
+pass in inet6 proto icmp6 all
+block in inet proto icmp all
+block in inet6 proto icmp6 all
+block return-rst in inet proto tcp all
+block return-rst in inet6 proto tcp all
+block return-rst(ttl 10) in inet proto tcp all
+block return-rst(ttl 10) in inet6 proto tcp all
+block return-icmp in inet proto icmp all
+block return-icmp(0) in inet proto icmp all
+block return-icmp(net-unr) in inet proto icmp all
+block return-icmp(5) in inet proto icmp all
+block return-icmp(srcfail) in inet proto icmp all
+block return-icmp(10) in inet proto icmp all
+block return-icmp(host-prohib) in inet proto icmp all
+block return-icmp(15) in inet proto icmp all
+block return-icmp(cutoff-preced) in inet proto icmp all
+block return-icmp6 in inet6 proto icmp6 all
+block return-icmp6(0) in inet6 proto icmp6 all
+block return-icmp6(noroute-unr) in inet6 proto icmp6 all
+block return-icmp6(1) in inet6 proto icmp6 all
+block return-icmp6(admin-unr) in inet6 proto icmp6 all
+block return-icmp6(2) in inet6 proto icmp6 all
+block return-icmp6(notnbr-unr) in inet6 proto icmp6 all
+block return-icmp6(3) in inet6 proto icmp6 all
+block return-icmp6(addr-unr) in inet6 proto icmp6 all
+block return-icmp6(4) in inet6 proto icmp6 all
+block return-icmp6(port-unr) in inet6 proto icmp6 all
+block return-icmp(5, 1) in all
+block return-icmp(srcfail, admin-unr) in all
diff --git a/sbin/pfctl/tests/files/pf0010.ok b/sbin/pfctl/tests/files/pf0010.ok
new file mode 100644
index 000000000000..4003c2306e93
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0010.ok
@@ -0,0 +1,30 @@
+pass in inet proto icmp all keep state
+pass in inet6 proto ipv6-icmp all keep state
+block drop in inet proto icmp all
+block drop in inet6 proto ipv6-icmp all
+block return-rst in inet proto tcp all
+block return-rst in inet6 proto tcp all
+block return-rst(ttl 10) in inet proto tcp all
+block return-rst(ttl 10) in inet6 proto tcp all
+block return-icmp(port-unr) in inet proto icmp all
+block return-icmp(net-unr) in inet proto icmp all
+block return-icmp(net-unr) in inet proto icmp all
+block return-icmp(srcfail) in inet proto icmp all
+block return-icmp(srcfail) in inet proto icmp all
+block return-icmp(host-prohib) in inet proto icmp all
+block return-icmp(host-prohib) in inet proto icmp all
+block return-icmp(cutoff-preced) in inet proto icmp all
+block return-icmp(cutoff-preced) in inet proto icmp all
+block return-icmp6(port-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(noroute-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(noroute-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(admin-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(admin-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(notnbr-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(notnbr-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(addr-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(addr-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(port-unr) in inet6 proto ipv6-icmp all
+block return-icmp6(port-unr) in inet6 proto ipv6-icmp all
+block return-icmp(srcfail, admin-unr) in all
+block return-icmp(srcfail, admin-unr) in all
diff --git a/sbin/pfctl/tests/files/pf0011.in b/sbin/pfctl/tests/files/pf0011.in
new file mode 100644
index 000000000000..a4dd3d574871
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0011.in
@@ -0,0 +1,18 @@
+pass in inet proto icmp all icmp-type 0
+pass in inet proto icmp all icmp-type 0 code 0
+pass in inet proto icmp all icmp-type 1
+pass in inet proto icmp all icmp-type 1 code 1
+pass in inet6 proto ipv6-icmp all icmp6-type 0
+pass in inet6 proto ipv6-icmp all icmp6-type 0 code 0
+pass in inet6 proto ipv6-icmp all icmp6-type 1
+pass in inet6 proto ipv6-icmp all icmp6-type 1 code 1
+block in inet proto icmp all icmp-type 0
+block in inet proto icmp all icmp-type 0 code 0
+block in inet proto icmp all icmp-type 1
+block in inet proto icmp all icmp-type 1 code 1
+block in inet6 proto ipv6-icmp all icmp6-type 0
+block in inet6 proto ipv6-icmp all icmp6-type 0 code 0
+block in inet6 proto ipv6-icmp all icmp6-type 1
+block in inet6 proto ipv6-icmp all icmp6-type 1 code 1
+pass in inet proto icmp all icmp-type unreach code needfrag
+pass in inet6 proto ipv6-icmp all icmp6-type timex code reassemb
diff --git a/sbin/pfctl/tests/files/pf0011.ok b/sbin/pfctl/tests/files/pf0011.ok
new file mode 100644
index 000000000000..1268e772db26
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0011.ok
@@ -0,0 +1,18 @@
+pass in inet proto icmp all icmp-type echorep keep state
+pass in inet proto icmp all icmp-type echorep code 0 keep state
+pass in inet proto icmp all icmp-type 1 keep state
+pass in inet proto icmp all icmp-type 1 code 1 keep state
+pass in inet6 proto ipv6-icmp all icmp6-type 0 keep state
+pass in inet6 proto ipv6-icmp all icmp6-type 0 code 0 keep state
+pass in inet6 proto ipv6-icmp all icmp6-type unreach keep state
+pass in inet6 proto ipv6-icmp all icmp6-type unreach code admin-unr keep state
+block drop in inet proto icmp all icmp-type echorep
+block drop in inet proto icmp all icmp-type echorep code 0
+block drop in inet proto icmp all icmp-type 1
+block drop in inet proto icmp all icmp-type 1 code 1
+block drop in inet6 proto ipv6-icmp all icmp6-type 0
+block drop in inet6 proto ipv6-icmp all icmp6-type 0 code 0
+block drop in inet6 proto ipv6-icmp all icmp6-type unreach
+block drop in inet6 proto ipv6-icmp all icmp6-type unreach code admin-unr
+pass in inet proto icmp all icmp-type unreach code needfrag keep state
+pass in inet6 proto ipv6-icmp all icmp6-type timex code reassemb keep state
diff --git a/sbin/pfctl/tests/files/pf0012.in b/sbin/pfctl/tests/files/pf0012.in
new file mode 100644
index 000000000000..15e4eae6af66
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0012.in
@@ -0,0 +1,5 @@
+pass in from 127.0.0.1 to 127.0.0.1/8 no state
+pass in from 127.0.0.1/16 to 127.0.0.1/24 no state
+pass in from 127.0.0.1/25 to ! 127.0.0.1/26
+pass in inet from ! localhost to localhost/16
+pass in inet from ! lo0 to ! lo0/8
diff --git a/sbin/pfctl/tests/files/pf0012.ok b/sbin/pfctl/tests/files/pf0012.ok
new file mode 100644
index 000000000000..4ca6765f377d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0012.ok
@@ -0,0 +1,5 @@
+pass in inet from 127.0.0.1 to 127.0.0.0/8 no state
+pass in inet from 127.0.0.0/16 to 127.0.0.0/24 no state
+pass in inet from 127.0.0.0/25 to ! 127.0.0.0/26 flags S/SA keep state
+pass in inet from ! 127.0.0.1 to 127.0.0.0/16 flags S/SA keep state
+pass in inet from ! 127.0.0.1 to ! 127.0.0.0/8 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0013.in b/sbin/pfctl/tests/files/pf0013.in
new file mode 100644
index 000000000000..a0504019e07d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0013.in
@@ -0,0 +1,22 @@
+pass in quick on enc0 from any to any
+pass in quick on enc0 inet from any to any
+pass in quick on enc0 inet6 from any to any
+
+#pass out quick on tun1000000 inet from any to any route-to tun1000001
+#pass out quick on tun1000000 from any to 192.168.1.1 route-to tun1000001
+#pass out quick on tun1000000 from any to fec0::1 route-to tun1000001
+
+#pass in on tun1000000 proto tcp from any to any port = 21 dup-to (tun1000001 192.168.1.1)
+#pass in on tun1000000 proto tcp from any to any port = 21 dup-to (tun1000001 fec0::1)
+
+#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 route-to tun1000001
+#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 route-to tun1000001
+
+#pass in on tun1000000 proto tcp from any to any port = 21 reply-to (tun1000001 192.168.1.1)
+#pass in on tun1000000 proto tcp from any to any port = 21 reply-to (tun1000001 fec0::1)
+
+#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 reply-to tun1000001
+#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 reply-to tun1000001
+
+#pass in quick on tun1000000 from 192.168.1.1/32 to 10.1.1.1/32 dup-to (tun1000001 192.168.1.100)
+#pass in quick on tun1000000 from fec0::1/64 to fec1::2/128 dup-to (tun1000001 fec1::2)
diff --git a/sbin/pfctl/tests/files/pf0013.ok b/sbin/pfctl/tests/files/pf0013.ok
new file mode 100644
index 000000000000..9783e40518b9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0013.ok
@@ -0,0 +1,3 @@
+pass in quick on enc0 all flags S/SA keep state
+pass in quick on enc0 inet all flags S/SA keep state
+pass in quick on enc0 inet6 all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0014.in b/sbin/pfctl/tests/files/pf0014.in
new file mode 100644
index 000000000000..eaca6de0fbfc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0014.in
@@ -0,0 +1,6 @@
+pass in quick on lo0 from fe80::1%lo0 to fe80::1%lo0
+pass in quick from fe80::1%lo0 to fe80::1%lo0
+pass in quick from fe80::1%lo0 to any
+pass in quick from any to fe80::1%lo0
+pass in quick on lo0 from fe80::1%lo0 to any
+pass in quick on lo0 from any to fe80::1%lo0
diff --git a/sbin/pfctl/tests/files/pf0014.ok b/sbin/pfctl/tests/files/pf0014.ok
new file mode 100644
index 000000000000..15cc43ff77c4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0014.ok
@@ -0,0 +1,6 @@
+pass in quick on lo0 inet6 from fe80::1 to fe80::1 flags S/SA keep state
+pass in quick on lo0 inet6 from fe80::1 to fe80::1 flags S/SA keep state
+pass in quick on lo0 inet6 from fe80::1 to any flags S/SA keep state
+pass in quick on lo0 inet6 from any to fe80::1 flags S/SA keep state
+pass in quick on lo0 inet6 from fe80::1 to any flags S/SA keep state
+pass in quick on lo0 inet6 from any to fe80::1 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0016.in b/sbin/pfctl/tests/files/pf0016.in
new file mode 100644
index 000000000000..7dbc53aa6a21
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0016.in
@@ -0,0 +1,5 @@
+# Test rule order processing: should fail unless nat -> filter
+match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
+match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
+match on lo0 from 192.168.1.1 to any binat-to 10.0.0.1
+pass in on lo1000000 from any to any no state
diff --git a/sbin/pfctl/tests/files/pf0016.ok b/sbin/pfctl/tests/files/pf0016.ok
new file mode 100644
index 000000000000..d65374a16475
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0016.ok
@@ -0,0 +1,5 @@
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1
+match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1 static-port
+match in on lo0 inet from any to 10.0.0.1 rdr-to 192.168.1.1
+pass in on lo1000000 all no state
diff --git a/sbin/pfctl/tests/files/pf0018.in b/sbin/pfctl/tests/files/pf0018.in
new file mode 100644
index 000000000000..ab3c81f86c5f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0018.in
@@ -0,0 +1,19 @@
+# test nat
+
+TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }"
+TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }"
+
+match out on lo0 from 192.168.1.1 to any nat-to 10.0.0.1
+match out on lo0 proto tcp from 192.168.1.2 to any nat-to 10.0.0.2
+match out on lo0 proto udp from 192.168.1.3 to any nat-to 10.0.0.3
+match out on lo0 proto icmp from 192.168.1.4 to any nat-to 10.0.0.4
+
+match out on lo0 inet from $TEST_LIST1 to $TEST_LIST2 nat-to lo0
+
+match out on lo0 inet from 192.168.0.1/24 to any nat-to (lo0)
+
+match out on lo0 from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8
+
+match out on ! lo0 proto { udp, tcp } from any to any nat-to 10.0.0.8 static-port
+
+match out on { lo0, tun1000000 } from any to any nat-to 10.0.0.8
diff --git a/sbin/pfctl/tests/files/pf0018.ok b/sbin/pfctl/tests/files/pf0018.ok
new file mode 100644
index 000000000000..6ba137ae84f8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0018.ok
@@ -0,0 +1,21 @@
+TEST_LIST1 = "{ 192.168.1.5, 192.168.1.6, 192.168.1.7 }"
+TEST_LIST2 = "{ 172.6.1.1, 172.14.1.2/32, 172.16.2.0/24 }"
+match out on lo0 inet from 192.168.1.1 to any nat-to 10.0.0.1
+match out on lo0 inet proto tcp from 192.168.1.2 to any nat-to 10.0.0.2
+match out on lo0 inet proto udp from 192.168.1.3 to any nat-to 10.0.0.3
+match out on lo0 inet proto icmp from 192.168.1.4 to any nat-to 10.0.0.4
+match out on lo0 inet from 192.168.1.5 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.5 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.5 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.6 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.6.1.1 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.14.1.2 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.1.7 to 172.16.2.0/24 nat-to 127.0.0.1
+match out on lo0 inet from 192.168.0.0/24 to any nat-to (lo0) round-robin
+match out on lo0 inet from 192.168.1.8 to ! 172.17.0.0/16 nat-to 10.0.0.8
+match out on ! lo0 inet proto udp all nat-to 10.0.0.8 static-port
+match out on ! lo0 inet proto tcp all nat-to 10.0.0.8 static-port
+match out on lo0 inet all nat-to 10.0.0.8
+match out on tun1000000 inet all nat-to 10.0.0.8
diff --git a/sbin/pfctl/tests/files/pf0019.in b/sbin/pfctl/tests/files/pf0019.in
new file mode 100644
index 000000000000..e2bedbb64bd0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0019.in
@@ -0,0 +1,9 @@
+EVIL = "lo0"
+GOOD = "{ lo0, lo1000000 }"
+GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
+DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+
+match in on lo0 proto tcp from any to 1.2.3.4/32 port 2222 rdr-to 10.0.0.10 port 22
+
+# Test list processing
+match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0019.ok b/sbin/pfctl/tests/files/pf0019.ok
new file mode 100644
index 000000000000..a5afc374d19f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0019.ok
@@ -0,0 +1,13 @@
+EVIL = "lo0"
+GOOD = "{ lo0, lo1000000 }"
+GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
+DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+match in on lo0 inet proto tcp from any to 1.2.3.4 port = 2222 rdr-to 10.0.0.10 port 22
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0020.in b/sbin/pfctl/tests/files/pf0020.in
new file mode 100644
index 000000000000..c973785bc9c5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0020.in
@@ -0,0 +1,9 @@
+# Test whether list expansion in NAT/RDR works correctly
+
+EVIL = "lo0"
+GOOD = "{ lo0, lo1000000 }"
+GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
+DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+
+match out on $EVIL inet from $GOOD_NET to $DEST_NET nat-to $EVIL
+match in on $GOOD proto tcp from $GOOD_NET to $DEST_NET port 21 rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0020.ok b/sbin/pfctl/tests/files/pf0020.ok
new file mode 100644
index 000000000000..bd2c6cf2055d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0020.ok
@@ -0,0 +1,16 @@
+EVIL = "lo0"
+GOOD = "{ lo0, lo1000000 }"
+GOOD_NET = "{ 127.0.0.0/24, 10.0.1.0/24 }"
+DEST_NET = "{ 1.2.3.4/25, 2.4.6.8/30 }"
+match out on lo0 inet from 127.0.0.0/24 to 1.2.3.0/25 nat-to 127.0.0.1
+match out on lo0 inet from 127.0.0.0/24 to 2.4.6.8/30 nat-to 127.0.0.1
+match out on lo0 inet from 10.0.1.0/24 to 1.2.3.0/25 nat-to 127.0.0.1
+match out on lo0 inet from 10.0.1.0/24 to 2.4.6.8/30 nat-to 127.0.0.1
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo0 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 127.0.0.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 1.2.3.0/25 port = ftp rdr-to 127.0.0.1 port 8021
+match in on lo1000000 inet proto tcp from 10.0.1.0/24 to 2.4.6.8/30 port = ftp rdr-to 127.0.0.1 port 8021
diff --git a/sbin/pfctl/tests/files/pf0022.in b/sbin/pfctl/tests/files/pf0022.in
new file mode 100644
index 000000000000..602a085c59f0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0022.in
@@ -0,0 +1,8 @@
+set optimization aggressive
+set timeout { tcp.closing 6, tcp.opening 6 }
+set timeout tcp.first 6
+set limit states 500
+set limit {states 1000,frags 1000}
+set loginterface lo0
+set loginterface none
+set hostid 1
diff --git a/sbin/pfctl/tests/files/pf0022.ok b/sbin/pfctl/tests/files/pf0022.ok
new file mode 100644
index 000000000000..76940552aa3a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0022.ok
@@ -0,0 +1,10 @@
+set optimization aggressive
+set timeout tcp.closing 6
+set timeout tcp.opening 6
+set timeout tcp.first 6
+set limit states 500
+set limit states 1000
+set limit frags 1000
+set loginterface lo0
+set loginterface none
+set hostid 0x00000001
diff --git a/sbin/pfctl/tests/files/pf0023.in b/sbin/pfctl/tests/files/pf0023.in
new file mode 100644
index 000000000000..2adbe16c4a50
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0023.in
@@ -0,0 +1,2 @@
+#test negated interface matching
+block in on ! lo0 all
diff --git a/sbin/pfctl/tests/files/pf0023.ok b/sbin/pfctl/tests/files/pf0023.ok
new file mode 100644
index 000000000000..83a75fe716af
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0023.ok
@@ -0,0 +1 @@
+block drop in on ! lo0 all
diff --git a/sbin/pfctl/tests/files/pf0024.in b/sbin/pfctl/tests/files/pf0024.in
new file mode 100644
index 000000000000..73c204933633
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0024.in
@@ -0,0 +1,8 @@
+#test variable concat
+a="ssh"
+b="ftp"
+c=$a $b
+d=$a $b $a $b
+e=$a $b $b "test" $a $b
+
+pass in proto tcp from any to any port { $c }
diff --git a/sbin/pfctl/tests/files/pf0024.ok b/sbin/pfctl/tests/files/pf0024.ok
new file mode 100644
index 000000000000..c6ff2f037012
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0024.ok
@@ -0,0 +1,7 @@
+a = "ssh"
+b = "ftp"
+c = "ssh ftp"
+d = "ssh ftp ssh ftp"
+e = "ssh ftp ftp test ssh ftp"
+pass in proto tcp from any to any port = ssh flags S/SA keep state
+pass in proto tcp from any to any port = ftp flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0025.in b/sbin/pfctl/tests/files/pf0025.in
new file mode 100644
index 000000000000..28d1a335ccf8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0025.in
@@ -0,0 +1,4 @@
+antispoof for lo0
+antispoof log quick for lo0 inet
+antispoof for (lo0)
+antispoof log quick for (lo0) inet
diff --git a/sbin/pfctl/tests/files/pf0025.ok b/sbin/pfctl/tests/files/pf0025.ok
new file mode 100644
index 000000000000..f4fc7766dc02
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0025.ok
@@ -0,0 +1,5 @@
+block drop in on ! lo0 inet6 from ::1 to any
+block drop in on ! lo0 inet from 127.0.0.0/8 to any
+block drop in log quick on ! lo0 inet from 127.0.0.0/8 to any
+block drop in on ! lo0 from (lo0:network) to any
+block drop in log quick on ! lo0 inet from (lo0:network) to any
diff --git a/sbin/pfctl/tests/files/pf0026.in b/sbin/pfctl/tests/files/pf0026.in
new file mode 100644
index 000000000000..5799de5afe9e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0026.in
@@ -0,0 +1,2 @@
+block in on lo0 inet from ! (lo0) to any
+block out on lo0 inet from any to ! (lo0)
diff --git a/sbin/pfctl/tests/files/pf0026.ok b/sbin/pfctl/tests/files/pf0026.ok
new file mode 100644
index 000000000000..a9a281244a69
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0026.ok
@@ -0,0 +1,2 @@
+block drop in on lo0 inet from ! (lo0) to any
+block drop out on lo0 inet from any to ! (lo0)
diff --git a/sbin/pfctl/tests/files/pf0028.in b/sbin/pfctl/tests/files/pf0028.in
new file mode 100644
index 000000000000..cfcc0b952200
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0028.in
@@ -0,0 +1,7 @@
+# test logging keywords, and log quick/quick log order
+block in log (all) quick on lo0 all
+block in quick log on lo0 all
+block in quick log (all) on lo0 all
+block in log quick on lo0 all
+block in log on lo0 all
+block in log (all) on lo0 all
diff --git a/sbin/pfctl/tests/files/pf0028.ok b/sbin/pfctl/tests/files/pf0028.ok
new file mode 100644
index 000000000000..ff6ca332dff4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0028.ok
@@ -0,0 +1,6 @@
+block drop in log (all) quick on lo0 all
+block drop in log quick on lo0 all
+block drop in log (all) quick on lo0 all
+block drop in log quick on lo0 all
+block drop in log on lo0 all
+block drop in log (all) on lo0 all
diff --git a/sbin/pfctl/tests/files/pf0030.in b/sbin/pfctl/tests/files/pf0030.in
new file mode 100644
index 000000000000..8ea257809291
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0030.in
@@ -0,0 +1,7 @@
+#test line continuation
+
+block \
+ in \
+ on lo0 \
+ from any \
+ to any
diff --git a/sbin/pfctl/tests/files/pf0030.ok b/sbin/pfctl/tests/files/pf0030.ok
new file mode 100644
index 000000000000..11fb969bbb91
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0030.ok
@@ -0,0 +1 @@
+block drop in on lo0 all
diff --git a/sbin/pfctl/tests/files/pf0031.in b/sbin/pfctl/tests/files/pf0031.in
new file mode 100644
index 000000000000..c227829f1121
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0031.in
@@ -0,0 +1,21 @@
+set block-policy drop
+block return in on lo0 all
+block return in on lo0 inet all
+block return in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
+block in on lo0 all
+block in on lo0 inet all
+block in on lo0 inet6 all
+#set block-policy return
+block return in on lo0 all
+block return in on lo0 inet all
+block return in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
+block in on lo0 all
+block in on lo0 inet all
+block in on lo0 inet6 all
+
diff --git a/sbin/pfctl/tests/files/pf0031.ok b/sbin/pfctl/tests/files/pf0031.ok
new file mode 100644
index 000000000000..d19a2797da21
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0031.ok
@@ -0,0 +1,19 @@
+set block-policy drop
+block return in on lo0 all
+block return in on lo0 inet all
+block return in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
+block return in on lo0 all
+block return in on lo0 inet all
+block return in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
+block drop in on lo0 all
+block drop in on lo0 inet all
+block drop in on lo0 inet6 all
diff --git a/sbin/pfctl/tests/files/pf0032.in b/sbin/pfctl/tests/files/pf0032.in
new file mode 100644
index 000000000000..333dafa72dd8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0032.in
@@ -0,0 +1,7 @@
+pass in from 10/8 to any
+pass in from 10.1/8 to any
+pass in from 192.168.37.29/25 to any
+pass in from 192.168.37.29/24 to any
+pass in from 192.168.37.29/16 to any
+pass in from 192.168.37.29/8 to any
+
diff --git a/sbin/pfctl/tests/files/pf0032.ok b/sbin/pfctl/tests/files/pf0032.ok
new file mode 100644
index 000000000000..826ce61ebcb3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0032.ok
@@ -0,0 +1,6 @@
+pass in inet from 10.0.0.0/8 to any flags S/SA keep state
+pass in inet from 10.0.0.0/8 to any flags S/SA keep state
+pass in inet from 192.168.37.0/25 to any flags S/SA keep state
+pass in inet from 192.168.37.0/24 to any flags S/SA keep state
+pass in inet from 192.168.0.0/16 to any flags S/SA keep state
+pass in inet from 192.0.0.0/8 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0034.in b/sbin/pfctl/tests/files/pf0034.in
new file mode 100644
index 000000000000..e3248d281e70
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0034.in
@@ -0,0 +1,5 @@
+#mixed af, probability
+pass in from any to { 127.0.0.1, 2000::1 }
+pass in probability 0.5
+pass in probability 50%
+pass in inet6 proto tcp from ::1 probability 0.8%
diff --git a/sbin/pfctl/tests/files/pf0034.ok b/sbin/pfctl/tests/files/pf0034.ok
new file mode 100644
index 000000000000..a91f1ae50d2e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0034.ok
@@ -0,0 +1,5 @@
+pass in inet from any to 127.0.0.1 flags S/SA keep state
+pass in inet6 from any to 2000::1 flags S/SA keep state
+pass in all flags S/SA keep state probability 50%
+pass in all flags S/SA keep state probability 50%
+pass in inet6 proto tcp from ::1 to any flags S/SA keep state probability 0.8%
diff --git a/sbin/pfctl/tests/files/pf0035.in b/sbin/pfctl/tests/files/pf0035.in
new file mode 100644
index 000000000000..3d0ab8963297
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0035.in
@@ -0,0 +1,5 @@
+#test matching on tos
+
+intf = "lo0"
+pass out on $intf inet proto tcp from any to any port 22 tos 0x10
+pass out on $intf inet proto tcp from any to any port 22 tos 0x08
diff --git a/sbin/pfctl/tests/files/pf0035.ok b/sbin/pfctl/tests/files/pf0035.ok
new file mode 100644
index 000000000000..fb77ae59e523
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0035.ok
@@ -0,0 +1,3 @@
+intf = "lo0"
+pass out on lo0 inet proto tcp from any to any port = ssh flags S/SA tos 0x10 keep state
+pass out on lo0 inet proto tcp from any to any port = ssh flags S/SA tos 0x08 keep state
diff --git a/sbin/pfctl/tests/files/pf0038.in b/sbin/pfctl/tests/files/pf0038.in
new file mode 100644
index 000000000000..1e63d6e5e268
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0038.in
@@ -0,0 +1,5 @@
+# test
+
+pass in on tun1000000 proto tcp from any to any user bin
+pass in on tun1000000 proto tcp from any to any group bin
+pass in on tun1000000 proto tcp from any to any group wheel user root user bin
diff --git a/sbin/pfctl/tests/files/pf0038.ok b/sbin/pfctl/tests/files/pf0038.ok
new file mode 100644
index 000000000000..77e2ee63bf5a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0038.ok
@@ -0,0 +1,4 @@
+pass in on tun1000000 proto tcp all user = 3 flags S/SA keep state
+pass in on tun1000000 proto tcp all group = 7 flags S/SA keep state
+pass in on tun1000000 proto tcp all user = 3 group = 0 flags S/SA keep state
+pass in on tun1000000 proto tcp all user = 0 group = 0 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0039.in b/sbin/pfctl/tests/files/pf0039.in
new file mode 100644
index 000000000000..739f4efd4297
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0039.in
@@ -0,0 +1,25 @@
+#test random ordered opts
+
+body1="pass in log quick on lo0 inet proto icmp all "
+body2="pass in log quick on lo0 inet proto tcp all "
+o_user="user root "
+o_user2="user bin "
+o_group="group wheel "
+o_group2="group nobody "
+o_flags="flags S/SA "
+o_icmpspec="icmp-type 0 code 0 "
+o_tos="tos 0x08 "
+o_keep="keep state "
+o_fragment="fragment "
+o_allowopts="allow-opts "
+o_label="label blah"
+o_prio="set prio 2"
+
+$body2 $o_fragment $o_keep $o_label $o_tos
+$body2 $o_user $o_prio $o_tos $o_keep $o_group $o_label $o_allowopts \
+$o_user2 $o_group2
+$body1 $o_icmpspec $o_keep $o_label $o_prio
+$body2 $o_keep
+$body2 $o_label $o_keep $o_prio $o_tos
+$body1 $o_icmpspec $o_tos
+$body2 $o_flags $o_allowopts
diff --git a/sbin/pfctl/tests/files/pf0039.ok b/sbin/pfctl/tests/files/pf0039.ok
new file mode 100644
index 000000000000..524d9d1d9537
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0039.ok
@@ -0,0 +1,24 @@
+body1 = "pass in log quick on lo0 inet proto icmp all "
+body2 = "pass in log quick on lo0 inet proto tcp all "
+o_user = "user root "
+o_user2 = "user bin "
+o_group = "group wheel "
+o_group2 = "group nobody "
+o_flags = "flags S/SA "
+o_icmpspec = "icmp-type 0 code 0 "
+o_tos = "tos 0x08 "
+o_keep = "keep state "
+o_fragment = "fragment "
+o_allowopts = "allow-opts "
+o_label = "label blah"
+o_prio = "set prio 2"
+pass in log quick on lo0 inet proto tcp all tos 0x08 keep state fragment label "blah"
+pass in log quick on lo0 inet proto tcp all user = 3 group = 65534 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah"
+pass in log quick on lo0 inet proto tcp all user = 3 group = 0 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah"
+pass in log quick on lo0 inet proto tcp all user = 0 group = 65534 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah"
+pass in log quick on lo0 inet proto tcp all user = 0 group = 0 flags S/SA tos 0x08 set ( prio 2 ) keep state allow-opts label "blah"
+pass in log quick on lo0 inet proto icmp all icmp-type echorep code 0 set ( prio 2 ) keep state label "blah"
+pass in log quick on lo0 inet proto tcp all flags S/SA keep state
+pass in log quick on lo0 inet proto tcp all flags S/SA tos 0x08 set ( prio 2 ) keep state label "blah"
+pass in log quick on lo0 inet proto icmp all icmp-type echorep code 0 tos 0x08 keep state
+pass in log quick on lo0 inet proto tcp all flags S/SA keep state allow-opts
diff --git a/sbin/pfctl/tests/files/pf0040.in b/sbin/pfctl/tests/files/pf0040.in
new file mode 100644
index 000000000000..7d91ad447109
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0040.in
@@ -0,0 +1,20 @@
+block
+block return
+block return-rst proto tcp
+pass
+pass in no state
+pass out no state
+pass all no state
+block in all
+block out all
+block from any to any
+pass in from any to any
+pass out from any to any
+block on lo0
+pass on lo0 all
+block on lo0 from any to any
+pass proto tcp flags S/SA
+pass proto udp keep state
+pass in proto udp all keep state
+pass out proto udp from any to any keep state
+pass out on lo0 proto tcp from any to any port 25 keep state
diff --git a/sbin/pfctl/tests/files/pf0040.ok b/sbin/pfctl/tests/files/pf0040.ok
new file mode 100644
index 000000000000..1a740bb96470
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0040.ok
@@ -0,0 +1,20 @@
+block drop all
+block return all
+block return-rst proto tcp all
+pass all flags S/SA keep state
+pass in all no state
+pass out all no state
+pass all no state
+block drop in all
+block drop out all
+block drop all
+pass in all flags S/SA keep state
+pass out all flags S/SA keep state
+block drop on lo0 all
+pass on lo0 all flags S/SA keep state
+block drop on lo0 all
+pass proto tcp all flags S/SA keep state
+pass proto udp all keep state
+pass in proto udp all keep state
+pass out proto udp all keep state
+pass out on lo0 proto tcp from any to any port = smtp flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0041.in b/sbin/pfctl/tests/files/pf0041.in
new file mode 100644
index 000000000000..42987e7f0daa
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0041.in
@@ -0,0 +1,12 @@
+anchor foo
+anchor bar all
+anchor bar from any to any
+anchor foo inet
+anchor foo inet6
+anchor foo inet all
+anchor foo proto tcp
+anchor foo inet proto tcp from 10.1.2.3 port smtp to 10.2.3.4 port ssh
+anchor foobar inet6 proto udp from ::1 port 1 to ::1 port 2
+anchor filteropt out proto tcp to any port 22 user root
+anchor filteropt in proto tcp to (self) port 22 group sshd
+anchor filteropt out inet proto icmp all icmp-type echoreq
diff --git a/sbin/pfctl/tests/files/pf0041.ok b/sbin/pfctl/tests/files/pf0041.ok
new file mode 100644
index 000000000000..836c7459365c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0041.ok
@@ -0,0 +1,12 @@
+anchor "foo" all
+anchor "bar" all
+anchor "bar" all
+anchor "foo" inet all
+anchor "foo" inet6 all
+anchor "foo" inet all
+anchor "foo" proto tcp all
+anchor "foo" inet proto tcp from 10.1.2.3 port = smtp to 10.2.3.4 port = ssh
+anchor "foobar" inet6 proto udp from ::1 port = tcpmux to ::1 port = compressnet
+anchor "filteropt" out proto tcp from any to any port = ssh user = 0
+anchor "filteropt" in proto tcp from any to (self) port = ssh group = 22
+anchor "filteropt" out inet proto icmp all icmp-type echoreq
diff --git a/sbin/pfctl/tests/files/pf0047.in b/sbin/pfctl/tests/files/pf0047.in
new file mode 100644
index 000000000000..0fcfa14ebb32
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0047.in
@@ -0,0 +1,67 @@
+pass in on lo0 all label ""
+
+pass in all label "$if"
+pass in on lo0 all label "$if"
+pass in on lo0 all label "$if$if"
+
+pass in on lo0 all label "$srcaddr"
+pass in on lo0 from 0/0 to any label "$srcaddr"
+pass in on lo0 from 127.0.0.1 to any label "$srcaddr"
+pass in on lo0 from 127.0.0.1 to any label "$srcaddr$srcaddr"
+pass in on lo0 from 127.0.0.1 to any label ":$srcaddr:$srcaddr:"
+pass in on lo0 from 127.0.0.1/8 to any label "$srcaddr"
+pass in on lo0 from 127.0.0.1/16 to any label "$srcaddr$srcaddr"
+pass in on lo0 from 127.0.0.1/31 to any label ":$srcaddr:$srcaddr:"
+pass in on lo0 inet6 from fe80::1 to any label "$srcaddr"
+pass in on lo0 inet6 from fe80::1 to any label "$srcaddr$srcaddr"
+pass in on lo0 inet6 from fe80::1 to any label ":$srcaddr:$srcaddr:"
+pass in on lo0 inet6 from lo0/8 to any label "$srcaddr"
+pass in on lo0 inet6 from lo0/64 to any label "$srcaddr$srcaddr"
+pass in on lo0 inet6 from lo0/127 to any label ":$srcaddr:$srcaddr:"
+
+pass in on lo0 all label "!$dstaddr!"
+pass in on lo0 inet from any to (lo0) label "$dstaddr"
+pass in on lo0 inet from any to (lo0) label "$dstaddr$dstaddr"
+pass in on lo0 inet from any to (lo0) label " $dstaddr $dstaddr "
+pass in on lo0 from any to ! 127.0.0.1/8 label "$dstaddr"
+pass in on lo0 from any to ! 127.0.0.1/16 label "$dstaddr$dstaddr"
+pass in on lo0 from any to ! 127.0.0.1/31 label " $dstaddr $dstaddr "
+pass in on lo0 inet6 from any to ! (lo0) label "$dstaddr"
+pass in on lo0 inet6 from any to ! (lo0) label "$dstaddr$dstaddr"
+pass in on lo0 inet6 from any to ! (lo0) label " $dstaddr $dstaddr "
+pass in on lo0 inet6 from any to ! ::1/8 label "$dstaddr"
+pass in on lo0 inet6 from any to ! ::1/64 label "$dstaddr$dstaddr"
+pass in on lo0 inet6 from any to ! ::1/127 label " $dstaddr $dstaddr "
+
+pass in on lo0 all label "x$srcportx"
+pass in on lo0 proto tcp from any port = 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port 28 >< 29 to any label "$srcport"
+pass in on lo0 proto tcp from any port 28 <> 29 to any label "$srcport"
+pass in on lo0 proto tcp from any port 28:29 to any label "$srcport"
+pass in on lo0 proto tcp from any port != 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port < 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port <= 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port > 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port >= 28 to any label "$srcport"
+pass in on lo0 proto tcp from any port = 28 to any label "$srcport$srcport"
+pass in on lo0 proto tcp from any port = 28 to any label "$$srcport$$srcport$"
+
+pass in on lo0 all label "$dstport"
+pass in on lo0 proto udp from any to any port = 29 label "$dstport"
+pass in on lo0 proto udp from any to any port != 29 label "$dstport$dstport"
+pass in on lo0 proto udp from any to any port > 29 label "x$dstportx$dstportx"
+
+pass in on lo0 all label "$proto"
+pass in on lo0 proto esp all label "$proto"
+pass in on lo0 proto esp all label "$proto$proto"
+pass in on lo0 proto esp all label "-$proto-$proto-"
+pass in on lo0 proto 166 all label "$proto"
+pass in on lo0 proto 166 all label "$proto$proto"
+pass in on lo0 proto 166 all label "_$proto_$proto_"
+
+pass in on lo0 all label "$nr"
+pass in on lo0 all label "$nr$nr"
+pass in on lo0 all label "%$nr%$nr%"
+
+pass in on lo0 proto tcp from 127.0.0.1 port = 30 to 127.0.0.2 port = 44 \
+ label "if $if proto $proto $srcaddr $srcport $dstaddr $dstport"
diff --git a/sbin/pfctl/tests/files/pf0047.ok b/sbin/pfctl/tests/files/pf0047.ok
new file mode 100644
index 000000000000..12b93bb14e30
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0047.ok
@@ -0,0 +1,61 @@
+pass in on lo0 all flags S/SA keep state
+pass in all flags S/SA keep state label "any"
+pass in on lo0 all flags S/SA keep state label "lo0"
+pass in on lo0 all flags S/SA keep state label "lo0lo0"
+pass in on lo0 all flags S/SA keep state label "any"
+pass in on lo0 inet all flags S/SA keep state label "any"
+pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label "127.0.0.1"
+pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label "127.0.0.1127.0.0.1"
+pass in on lo0 inet from 127.0.0.1 to any flags S/SA keep state label ":127.0.0.1:127.0.0.1:"
+pass in on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state label "127.0.0.0/8"
+pass in on lo0 inet from 127.0.0.0/16 to any flags S/SA keep state label "127.0.0.0/16127.0.0.0/16"
+pass in on lo0 inet from 127.0.0.0/31 to any flags S/SA keep state label ":127.0.0.0/31:127.0.0.0/31:"
+pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label "fe80::1"
+pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label "fe80::1fe80::1"
+pass in on lo0 inet6 from fe80::1 to any flags S/SA keep state label ":fe80::1:fe80::1:"
+pass in on lo0 inet6 from ::/8 to any flags S/SA keep state label "::/8"
+pass in on lo0 inet6 from fe00::/8 to any flags S/SA keep state label "fe00::/8"
+pass in on lo0 inet6 from ::/64 to any flags S/SA keep state label "::/64::/64"
+pass in on lo0 inet6 from fe80::/64 to any flags S/SA keep state label "fe80::/64fe80::/64"
+pass in on lo0 inet6 from ::/127 to any flags S/SA keep state label ":::/127:::/127:"
+pass in on lo0 inet6 from fe80::/127 to any flags S/SA keep state label ":fe80::/127:fe80::/127:"
+pass in on lo0 all flags S/SA keep state label "!any!"
+pass in on lo0 inet from any to (lo0) flags S/SA keep state label "(lo0)"
+pass in on lo0 inet from any to (lo0) flags S/SA keep state label "(lo0)(lo0)"
+pass in on lo0 inet from any to (lo0) flags S/SA keep state label " (lo0) (lo0) "
+pass in on lo0 inet from any to ! 127.0.0.0/8 flags S/SA keep state label "! 127.0.0.0/8"
+pass in on lo0 inet from any to ! 127.0.0.0/16 flags S/SA keep state label "! 127.0.0.0/16! 127.0.0.0/16"
+pass in on lo0 inet from any to ! 127.0.0.0/31 flags S/SA keep state label " ! 127.0.0.0/31 ! 127.0.0.0/31 "
+pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label "! (lo0)"
+pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label "! (lo0)! (lo0)"
+pass in on lo0 inet6 from any to ! (lo0) flags S/SA keep state label " ! (lo0) ! (lo0) "
+pass in on lo0 inet6 from any to ! ::/8 flags S/SA keep state label "! ::/8"
+pass in on lo0 inet6 from any to ! ::/64 flags S/SA keep state label "! ::/64! ::/64"
+pass in on lo0 inet6 from any to ! ::/127 flags S/SA keep state label " ! ::/127 ! ::/127 "
+pass in on lo0 all flags S/SA keep state label "xx"
+pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "28"
+pass in on lo0 proto tcp from any port 28 >< 29 to any flags S/SA keep state label "28><29"
+pass in on lo0 proto tcp from any port 28 <> 29 to any flags S/SA keep state label "28<>29"
+pass in on lo0 proto tcp from any port 28:29 to any flags S/SA keep state
+pass in on lo0 proto tcp from any port != 28 to any flags S/SA keep state label "!=28"
+pass in on lo0 proto tcp from any port < 28 to any flags S/SA keep state label "<28"
+pass in on lo0 proto tcp from any port <= 28 to any flags S/SA keep state label "<=28"
+pass in on lo0 proto tcp from any port > 28 to any flags S/SA keep state label ">28"
+pass in on lo0 proto tcp from any port >= 28 to any flags S/SA keep state label ">=28"
+pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "2828"
+pass in on lo0 proto tcp from any port = 28 to any flags S/SA keep state label "$28$28$"
+pass in on lo0 all flags S/SA keep state
+pass in on lo0 proto udp from any to any port = msg-icp keep state label "29"
+pass in on lo0 proto udp from any to any port != msg-icp keep state label "!=29!=29"
+pass in on lo0 proto udp from any to any port > 29 keep state label "x>29x>29x"
+pass in on lo0 all flags S/SA keep state label "ip"
+pass in on lo0 proto esp all keep state label "esp"
+pass in on lo0 proto esp all keep state label "espesp"
+pass in on lo0 proto esp all keep state label "-esp-esp-"
+pass in on lo0 proto 166 all keep state label "166"
+pass in on lo0 proto 166 all keep state label "166166"
+pass in on lo0 proto 166 all keep state label "_166_166_"
+pass in on lo0 all flags S/SA keep state label "57"
+pass in on lo0 all flags S/SA keep state label "5858"
+pass in on lo0 all flags S/SA keep state label "%59%59%"
+pass in on lo0 inet proto tcp from 127.0.0.1 port = 30 to 127.0.0.2 port = mpm-flags flags S/SA keep state label "if lo0 proto tcp 127.0.0.1 30 127.0.0.2 44"
diff --git a/sbin/pfctl/tests/files/pf0048.in b/sbin/pfctl/tests/files/pf0048.in
new file mode 100644
index 000000000000..a0dd143c8dd2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0048.in
@@ -0,0 +1,13 @@
+table < regress > { 1.2.3.4 !5.6.7.8 10/8 lo0 }
+table <regress.1> const { ::1 fe80::/64 }
+table <regress.a> { 1.2.3.4 !5.6.7.8 } { ::1 ::2 ::3 } file "/dev/null" const { 4.3.2.1 }
+match out on lo0 inet from < regress.1> to <regress.2> nat-to lo0:0
+match out on !lo0 inet from !<regress.1 > to <regress.2> nat-to lo0:0
+match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to lo0:0
+match in on !lo0 inet6 from !< regress.1 > to <regress.2> rdr-to lo0:0
+match in from { <regress.1> !<regress.2> } to any
+match out from any to { !<regress.1>, <regress.2> }
+pass in from <regress> to any
+pass out from any to <regress >
+pass in from { <regress.1> <regress.2> } to any
+pass out from any to { !<regress.1>, !<regress.2> }
diff --git a/sbin/pfctl/tests/files/pf0048.ok b/sbin/pfctl/tests/files/pf0048.ok
new file mode 100644
index 000000000000..89569fb4f8ba
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0048.ok
@@ -0,0 +1,17 @@
+table <regress> { 1.2.3.4 !5.6.7.8 10.0.0.0/8 ::1 fe80::1 127.0.0.1 }
+table <regress.1> const { ::1 fe80::/64 }
+table <regress.a> const { 1.2.3.4 !5.6.7.8 ::1 ::2 ::3 } file "/dev/null" { 4.3.2.1 }
+match out on lo0 inet from <regress.1> to <regress.2> nat-to 127.0.0.1
+match out on ! lo0 inet from ! <regress.1> to <regress.2> nat-to 127.0.0.1
+match in on lo0 inet6 from <regress.1> to <regress.2> rdr-to ::1
+match in on ! lo0 inet6 from ! <regress.1> to <regress.2> rdr-to ::1
+match in from <regress.1> to any
+match in from ! <regress.2> to any
+match out from any to ! <regress.1>
+match out from any to <regress.2>
+pass in from <regress> to any flags S/SA keep state
+pass out from any to <regress> flags S/SA keep state
+pass in from <regress.1> to any flags S/SA keep state
+pass in from <regress.2> to any flags S/SA keep state
+pass out from any to ! <regress.1> flags S/SA keep state
+pass out from any to ! <regress.2> flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0049.in b/sbin/pfctl/tests/files/pf0049.in
new file mode 100644
index 000000000000..91b9712f7b30
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0049.in
@@ -0,0 +1,7 @@
+#test :broadcast and :network modifiers
+pass in on lo0 from lo0:network to any keep state
+pass out on lo0 inet from lo0:network to any
+pass in on lo0 inet6 from lo0:network to any keep state
+
+#broadcast on lo0 doesn't make sense at all!
+#block in on lo0 from any to lo0:broadcast
diff --git a/sbin/pfctl/tests/files/pf0049.ok b/sbin/pfctl/tests/files/pf0049.ok
new file mode 100644
index 000000000000..0349424cee1e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0049.ok
@@ -0,0 +1,4 @@
+pass in on lo0 inet6 from ::1 to any flags S/SA keep state
+pass in on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state
+pass out on lo0 inet from 127.0.0.0/8 to any flags S/SA keep state
+pass in on lo0 inet6 from ::1 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0050.in b/sbin/pfctl/tests/files/pf0050.in
new file mode 100644
index 000000000000..e1ecb5274b1e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0050.in
@@ -0,0 +1,4 @@
+# double macro set
+extif="wi0"
+extif="lo0"
+block in on $extif
diff --git a/sbin/pfctl/tests/files/pf0050.ok b/sbin/pfctl/tests/files/pf0050.ok
new file mode 100644
index 000000000000..e891b238639b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0050.ok
@@ -0,0 +1,3 @@
+extif = "wi0"
+extif = "lo0"
+block drop in on lo0 all
diff --git a/sbin/pfctl/tests/files/pf0052.in b/sbin/pfctl/tests/files/pf0052.in
new file mode 100644
index 000000000000..262d029841d3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0052.in
@@ -0,0 +1,7 @@
+# test setting all optimizations to avoid future keyword clashes
+
+set optimization normal
+set optimization satellite
+set optimization high-latency
+set optimization conservative
+set optimization aggressive
diff --git a/sbin/pfctl/tests/files/pf0052.ok b/sbin/pfctl/tests/files/pf0052.ok
new file mode 100644
index 000000000000..f83263b2a267
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0052.ok
@@ -0,0 +1,5 @@
+set optimization normal
+set optimization satellite
+set optimization high-latency
+set optimization conservative
+set optimization aggressive
diff --git a/sbin/pfctl/tests/files/pf0053.in b/sbin/pfctl/tests/files/pf0053.in
new file mode 100644
index 000000000000..263f99048f1d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0053.in
@@ -0,0 +1,4 @@
+pass in proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \
+"$nr:$if:$proto:$srcaddr:$srcport:$dstaddr:$dstport"
+pass in on lo0 proto tcp from { 1.2.3.4, 1.2.3.5 } to any label \
+"$nr:$if:$proto:$srcaddr:$srcport:$dstaddr:$dstport"
diff --git a/sbin/pfctl/tests/files/pf0053.ok b/sbin/pfctl/tests/files/pf0053.ok
new file mode 100644
index 000000000000..91866b724d31
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0053.ok
@@ -0,0 +1,4 @@
+pass in inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "0:any:tcp:1.2.3.4::any:"
+pass in inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "1:any:tcp:1.2.3.5::any:"
+pass in on lo0 inet proto tcp from 1.2.3.4 to any flags S/SA keep state label "2:lo0:tcp:1.2.3.4::any:"
+pass in on lo0 inet proto tcp from 1.2.3.5 to any flags S/SA keep state label "3:lo0:tcp:1.2.3.5::any:"
diff --git a/sbin/pfctl/tests/files/pf0055.in b/sbin/pfctl/tests/files/pf0055.in
new file mode 100644
index 000000000000..849221e316a7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0055.in
@@ -0,0 +1,18 @@
+set timeout { interval 43, frag 23 }
+set timeout { tcp.first 423, tcp.opening 123, tcp.established 43758 }
+set timeout { tcp.closing 744, tcp.finwait 25, tcp.closed 38 }
+set timeout { udp.first 356, udp.single 73, udp.multiple 34 }
+set timeout { icmp.first 464, icmp.error 34 }
+set timeout { other.first 455, other.single 54, other.multiple 324 }
+set timeout { src.track 3600 }
+set limit { states 4522, frags 43556 }
+set loginterface none
+set loginterface lo0
+set hostid 1
+set optimization normal
+set block-policy drop
+
+set limit states 43254
+set limit frags 34557
+set timeout interval 344
+set timeout frag 213
diff --git a/sbin/pfctl/tests/files/pf0055.ok b/sbin/pfctl/tests/files/pf0055.ok
new file mode 100644
index 000000000000..2281ca82abd4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0055.ok
@@ -0,0 +1,28 @@
+set timeout interval 43
+set timeout frag 23
+set timeout tcp.first 423
+set timeout tcp.opening 123
+set timeout tcp.established 43758
+set timeout tcp.closing 744
+set timeout tcp.finwait 25
+set timeout tcp.closed 38
+set timeout udp.first 356
+set timeout udp.single 73
+set timeout udp.multiple 34
+set timeout icmp.first 464
+set timeout icmp.error 34
+set timeout other.first 455
+set timeout other.single 54
+set timeout other.multiple 324
+set timeout src.track 3600
+set limit states 4522
+set limit frags 43556
+set loginterface none
+set loginterface lo0
+set hostid 0x00000001
+set optimization normal
+set block-policy drop
+set limit states 43254
+set limit frags 34557
+set timeout interval 344
+set timeout frag 213
diff --git a/sbin/pfctl/tests/files/pf0056.in b/sbin/pfctl/tests/files/pf0056.in
new file mode 100644
index 000000000000..691908925488
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0056.in
@@ -0,0 +1,2 @@
+pass in proto tcp from any to any port www keep state (tcp.established 60)
+pass in proto tcp from any to any port www keep state (max 10, no-sync, tcp.first 2)
diff --git a/sbin/pfctl/tests/files/pf0056.ok b/sbin/pfctl/tests/files/pf0056.ok
new file mode 100644
index 000000000000..14bf215a4d7d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0056.ok
@@ -0,0 +1,2 @@
+pass in proto tcp from any to any port = http flags S/SA keep state (tcp.established 60)
+pass in proto tcp from any to any port = http flags S/SA keep state (max 10, no-sync, tcp.first 2, adaptive.start 6, adaptive.end 12)
diff --git a/sbin/pfctl/tests/files/pf0057.in b/sbin/pfctl/tests/files/pf0057.in
new file mode 100644
index 000000000000..0eca99d162f0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0057.in
@@ -0,0 +1,4 @@
+a="10.0.0.1"
+b="x"
+b="y"
+pass in from $a
diff --git a/sbin/pfctl/tests/files/pf0057.ok b/sbin/pfctl/tests/files/pf0057.ok
new file mode 100644
index 000000000000..23299e285181
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0057.ok
@@ -0,0 +1,4 @@
+a = "10.0.0.1"
+b = "x"
+b = "y"
+pass in inet from 10.0.0.1 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0060.in b/sbin/pfctl/tests/files/pf0060.in
new file mode 100644
index 000000000000..2824cfd301b2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0060.in
@@ -0,0 +1,11 @@
+# netmask handling w/ multicast
+
+pass from 224.4.5.4/32
+pass from 224.4.5.4/16
+pass from 224.4.5.4/26
+pass from 224.4.5.65/26
+pass from 224.4.5.134/26
+pass from 224.4.5.199/26
+pass from 224.4.5.4
+
+
diff --git a/sbin/pfctl/tests/files/pf0060.ok b/sbin/pfctl/tests/files/pf0060.ok
new file mode 100644
index 000000000000..f0cd27039fef
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0060.ok
@@ -0,0 +1,7 @@
+pass inet from 224.4.5.4 to any flags S/SA keep state
+pass inet from 224.4.0.0/16 to any flags S/SA keep state
+pass inet from 224.4.5.0/26 to any flags S/SA keep state
+pass inet from 224.4.5.64/26 to any flags S/SA keep state
+pass inet from 224.4.5.128/26 to any flags S/SA keep state
+pass inet from 224.4.5.192/26 to any flags S/SA keep state
+pass inet from 224.4.5.4 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0061.in b/sbin/pfctl/tests/files/pf0061.in
new file mode 100644
index 000000000000..7343a39ee64b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0061.in
@@ -0,0 +1,4 @@
+# dynaddr with netmask
+
+pass inet to (lo0)/24
+
diff --git a/sbin/pfctl/tests/files/pf0061.ok b/sbin/pfctl/tests/files/pf0061.ok
new file mode 100644
index 000000000000..f28451aa473d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0061.ok
@@ -0,0 +1 @@
+pass inet from any to (lo0)/24 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0065.in b/sbin/pfctl/tests/files/pf0065.in
new file mode 100644
index 000000000000..617ba5f51e0e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0065.in
@@ -0,0 +1,2 @@
+antispoof for lo0 label "antispoof-lo0"
+antispoof log quick for lo0 inet label "antispoof-lo0-2"
diff --git a/sbin/pfctl/tests/files/pf0065.ok b/sbin/pfctl/tests/files/pf0065.ok
new file mode 100644
index 000000000000..eaef6485bcd5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0065.ok
@@ -0,0 +1,3 @@
+block drop in on ! lo0 inet6 from ::1 to any label "antispoof-lo0"
+block drop in on ! lo0 inet from 127.0.0.0/8 to any label "antispoof-lo0"
+block drop in log quick on ! lo0 inet from 127.0.0.0/8 to any label "antispoof-lo0-2"
diff --git a/sbin/pfctl/tests/files/pf0067.in b/sbin/pfctl/tests/files/pf0067.in
new file mode 100644
index 000000000000..4594420aff0c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0067.in
@@ -0,0 +1,3 @@
+pass in quick on tun1000000 keep state tag regress
+pass out quick on lo0 keep state tagged regress
+
diff --git a/sbin/pfctl/tests/files/pf0067.ok b/sbin/pfctl/tests/files/pf0067.ok
new file mode 100644
index 000000000000..4b09611f9a06
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0067.ok
@@ -0,0 +1,2 @@
+pass in quick on tun1000000 all flags S/SA keep state tag regress
+pass out quick on lo0 all flags S/SA keep state tagged regress
diff --git a/sbin/pfctl/tests/files/pf0069.in b/sbin/pfctl/tests/files/pf0069.in
new file mode 100644
index 000000000000..85847b9bd6b2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0069.in
@@ -0,0 +1,2 @@
+match out on lo0 inet all tag regress nat-to lo0
+pass out quick on lo0 keep state tagged regress
diff --git a/sbin/pfctl/tests/files/pf0069.ok b/sbin/pfctl/tests/files/pf0069.ok
new file mode 100644
index 000000000000..2bf34c04baa7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0069.ok
@@ -0,0 +1,2 @@
+match out on lo0 inet all tag regress nat-to 127.0.0.1
+pass out quick on lo0 all flags S/SA keep state tagged regress
diff --git a/sbin/pfctl/tests/files/pf0070.in b/sbin/pfctl/tests/files/pf0070.in
new file mode 100644
index 000000000000..1ccec9302436
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0070.in
@@ -0,0 +1,2 @@
+match out on lo0 from 10.0.0.0/8 to any nat-to lo0
+block out on lo0 tagged regress
diff --git a/sbin/pfctl/tests/files/pf0070.ok b/sbin/pfctl/tests/files/pf0070.ok
new file mode 100644
index 000000000000..cf79485b40c1
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0070.ok
@@ -0,0 +1,2 @@
+match out on lo0 inet from 10.0.0.0/8 to any nat-to 127.0.0.1
+block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0071.in b/sbin/pfctl/tests/files/pf0071.in
new file mode 100644
index 000000000000..8975a8ebc943
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0071.in
@@ -0,0 +1,2 @@
+match in on lo0 proto tcp from 10.0.0.0/8 to port 80 rdr-to lo0
+block out on lo0 tagged regress
diff --git a/sbin/pfctl/tests/files/pf0071.ok b/sbin/pfctl/tests/files/pf0071.ok
new file mode 100644
index 000000000000..2bae94fc8fac
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0071.ok
@@ -0,0 +1,2 @@
+match in on lo0 inet proto tcp from 10.0.0.0/8 to any port = http rdr-to 127.0.0.1
+block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0072.in b/sbin/pfctl/tests/files/pf0072.in
new file mode 100644
index 000000000000..d23843b799d5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0072.in
@@ -0,0 +1,3 @@
+# test binat tagging
+match on lo0 from 192.168.1.1 to any tag regress binat-to 10.0.0.1
+block out on lo0 tagged regress
diff --git a/sbin/pfctl/tests/files/pf0072.ok b/sbin/pfctl/tests/files/pf0072.ok
new file mode 100644
index 000000000000..02e676dadc06
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0072.ok
@@ -0,0 +1,3 @@
+match out on lo0 inet from 192.168.1.1 to any tag regress nat-to 10.0.0.1 static-port
+match in on lo0 inet from any to 10.0.0.1 tag regress rdr-to 192.168.1.1
+block drop out on lo0 all tagged regress
diff --git a/sbin/pfctl/tests/files/pf0074.in b/sbin/pfctl/tests/files/pf0074.in
new file mode 100644
index 000000000000..521bdd00c889
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0074.in
@@ -0,0 +1 @@
+pass in proto tcp synproxy state
diff --git a/sbin/pfctl/tests/files/pf0074.ok b/sbin/pfctl/tests/files/pf0074.ok
new file mode 100644
index 000000000000..1f5d99dfe106
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0074.ok
@@ -0,0 +1 @@
+pass in proto tcp all flags S/SA synproxy state
diff --git a/sbin/pfctl/tests/files/pf0075.in b/sbin/pfctl/tests/files/pf0075.in
new file mode 100644
index 000000000000..ee12db7b10cf
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0075.in
@@ -0,0 +1,3 @@
+block in on lo0 proto tcp from 192.168.0.0/24 to port 22 tag ssh
+block in quick on lo0 ! tagged ssh
+ \ No newline at end of file
diff --git a/sbin/pfctl/tests/files/pf0075.ok b/sbin/pfctl/tests/files/pf0075.ok
new file mode 100644
index 000000000000..460715b5dd2d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0075.ok
@@ -0,0 +1,2 @@
+block drop in on lo0 inet proto tcp from 192.168.0.0/24 to any port = ssh tag ssh
+block drop in quick on lo0 all ! tagged ssh
diff --git a/sbin/pfctl/tests/files/pf0077.in b/sbin/pfctl/tests/files/pf0077.in
new file mode 100644
index 000000000000..b6e32e15a9e7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0077.in
@@ -0,0 +1,5 @@
+# dynaddr with netmask. I never want to see this again:
+# <henning@quigon:1>$ echo "pass inet from (le0)/8" | pfctl -nvf -
+# pass inet from (l)/8 to any
+
+pass inet from (lo0)/8
diff --git a/sbin/pfctl/tests/files/pf0077.ok b/sbin/pfctl/tests/files/pf0077.ok
new file mode 100644
index 000000000000..233d434b782b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0077.ok
@@ -0,0 +1 @@
+pass inet from (lo0)/8 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0078.in b/sbin/pfctl/tests/files/pf0078.in
new file mode 100644
index 000000000000..0b2368c72c0e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0078.in
@@ -0,0 +1,2 @@
+pass in from 10.0.0.1 to <regress> label "$srcaddr:$dstaddr"
+
diff --git a/sbin/pfctl/tests/files/pf0078.ok b/sbin/pfctl/tests/files/pf0078.ok
new file mode 100644
index 000000000000..fed726e4f671
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0078.ok
@@ -0,0 +1 @@
+pass in inet from 10.0.0.1 to <regress> flags S/SA keep state label "10.0.0.1:<regress>"
diff --git a/sbin/pfctl/tests/files/pf0079.in b/sbin/pfctl/tests/files/pf0079.in
new file mode 100644
index 000000000000..402266be8a72
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0079.in
@@ -0,0 +1,2 @@
+pass in from 10.0.0.1 to no-route label "$srcaddr:$dstaddr"
+
diff --git a/sbin/pfctl/tests/files/pf0079.ok b/sbin/pfctl/tests/files/pf0079.ok
new file mode 100644
index 000000000000..a21475d63ec8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0079.ok
@@ -0,0 +1 @@
+pass in inet from 10.0.0.1 to no-route flags S/SA keep state label "10.0.0.1:no-route"
diff --git a/sbin/pfctl/tests/files/pf0081.in b/sbin/pfctl/tests/files/pf0081.in
new file mode 100644
index 000000000000..ac25c49dc65d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0081.in
@@ -0,0 +1,12 @@
+# skip step optimization involving dynaddr, tables, no-route
+# optimisation should be done on theses rules
+
+ip_list="{ ::1 ::2 ::3 0.0.0.1 0.0.0.2 0.0.0.3 }"
+table_list="{ <bar1> <bar2> <bar3> }"
+pass from (lo0) to $ip_list
+pass from <foo> to $table_list
+pass from <foo> to $ip_list
+pass from <foo> to $table_list
+pass from no-route to $table_list
+pass from no-route to $ip_list
+pass from no-route to $table_list
diff --git a/sbin/pfctl/tests/files/pf0081.ok b/sbin/pfctl/tests/files/pf0081.ok
new file mode 100644
index 000000000000..2b58a18744d9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0081.ok
@@ -0,0 +1,32 @@
+ip_list = "{ ::1 ::2 ::3 0.0.0.1 0.0.0.2 0.0.0.3 }"
+table_list = "{ <bar1> <bar2> <bar3> }"
+pass inet6 from (lo0) to ::1 flags S/SA keep state
+pass inet6 from (lo0) to ::2 flags S/SA keep state
+pass inet6 from (lo0) to ::3 flags S/SA keep state
+pass inet from (lo0) to 0.0.0.1 flags S/SA keep state
+pass inet from (lo0) to 0.0.0.2 flags S/SA keep state
+pass inet from (lo0) to 0.0.0.3 flags S/SA keep state
+pass from <foo> to <bar1> flags S/SA keep state
+pass from <foo> to <bar2> flags S/SA keep state
+pass from <foo> to <bar3> flags S/SA keep state
+pass inet6 from <foo> to ::1 flags S/SA keep state
+pass inet6 from <foo> to ::2 flags S/SA keep state
+pass inet6 from <foo> to ::3 flags S/SA keep state
+pass inet from <foo> to 0.0.0.1 flags S/SA keep state
+pass inet from <foo> to 0.0.0.2 flags S/SA keep state
+pass inet from <foo> to 0.0.0.3 flags S/SA keep state
+pass from <foo> to <bar1> flags S/SA keep state
+pass from <foo> to <bar2> flags S/SA keep state
+pass from <foo> to <bar3> flags S/SA keep state
+pass from no-route to <bar1> flags S/SA keep state
+pass from no-route to <bar2> flags S/SA keep state
+pass from no-route to <bar3> flags S/SA keep state
+pass inet6 from no-route to ::1 flags S/SA keep state
+pass inet6 from no-route to ::2 flags S/SA keep state
+pass inet6 from no-route to ::3 flags S/SA keep state
+pass inet from no-route to 0.0.0.1 flags S/SA keep state
+pass inet from no-route to 0.0.0.2 flags S/SA keep state
+pass inet from no-route to 0.0.0.3 flags S/SA keep state
+pass from no-route to <bar1> flags S/SA keep state
+pass from no-route to <bar2> flags S/SA keep state
+pass from no-route to <bar3> flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0082.in b/sbin/pfctl/tests/files/pf0082.in
new file mode 100644
index 000000000000..7f1751deb365
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0082.in
@@ -0,0 +1,15 @@
+# skip step optimization involving dynaddr, tables, no-route
+
+pass inet from (lo0)
+pass inet from !(lo0)
+pass inet from (lo0)
+pass inet6 from (lo0)
+pass from <foo>
+pass from !<foo>
+pass from <foo>
+pass inet from <bar>
+pass from <bar>
+pass inet6 from <foo>
+pass from <foo>
+pass inet from no-route
+pass from no-route
diff --git a/sbin/pfctl/tests/files/pf0082.ok b/sbin/pfctl/tests/files/pf0082.ok
new file mode 100644
index 000000000000..4a2071521a35
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0082.ok
@@ -0,0 +1,13 @@
+pass inet from (lo0) to any flags S/SA keep state
+pass inet from ! (lo0) to any flags S/SA keep state
+pass inet from (lo0) to any flags S/SA keep state
+pass inet6 from (lo0) to any flags S/SA keep state
+pass from <foo> to any flags S/SA keep state
+pass from ! <foo> to any flags S/SA keep state
+pass from <foo> to any flags S/SA keep state
+pass inet from <bar> to any flags S/SA keep state
+pass from <bar> to any flags S/SA keep state
+pass inet6 from <foo> to any flags S/SA keep state
+pass from <foo> to any flags S/SA keep state
+pass inet from no-route to any flags S/SA keep state
+pass from no-route to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0084.in b/sbin/pfctl/tests/files/pf0084.in
new file mode 100644
index 000000000000..17140a786d73
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0084.in
@@ -0,0 +1,17 @@
+match out on tun1000000 from 10.0.0.0/24 to any \
+ nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address
+match in on tun1000000 from any to 10.0.1.1 \
+ rdr-to { 10.0.0.0/24 } sticky-address random
+match in on tun1000000 from any to 10.0.1.2 \
+ rdr-to { 10.0.0.1, 10.0.0.2 } sticky-address
+
+pass in proto tcp from any to any port 22 \
+ keep state (source-track)
+pass in proto tcp from any to any port 25 \
+ keep state (source-track global)
+pass in proto tcp from any to any port 80 \
+ keep state (source-track rule, max-src-nodes 1000, max-src-states 3)
+pass in proto tcp from any to any port 123 \
+ keep state (source-track, max-src-nodes 1000)
+pass in proto tcp from any to any port 321 \
+ keep state (source-track, max-src-states 3)
diff --git a/sbin/pfctl/tests/files/pf0084.ok b/sbin/pfctl/tests/files/pf0084.ok
new file mode 100644
index 000000000000..1ca89e515a3d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0084.ok
@@ -0,0 +1,8 @@
+match out on tun1000000 inet from 10.0.0.0/24 to any nat-to { 10.0.1.1, 10.0.1.2 } round-robin sticky-address
+match in on tun1000000 inet from any to 10.0.1.1 rdr-to 10.0.0.0/24 random sticky-address
+match in on tun1000000 inet from any to 10.0.1.2 rdr-to { 10.0.0.1, 10.0.0.2 } round-robin sticky-address
+pass in proto tcp from any to any port = ssh flags S/SA keep state (source-track global)
+pass in proto tcp from any to any port = smtp flags S/SA keep state (source-track global)
+pass in proto tcp from any to any port = http flags S/SA keep state (source-track rule, max-src-states 3, max-src-nodes 1000)
+pass in proto tcp from any to any port = ntp flags S/SA keep state (source-track rule, max-src-nodes 1000)
+pass in proto tcp from any to any port = pip flags S/SA keep state (source-track global, max-src-states 3)
diff --git a/sbin/pfctl/tests/files/pf0085.in b/sbin/pfctl/tests/files/pf0085.in
new file mode 100644
index 000000000000..43dd0e077658
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0085.in
@@ -0,0 +1,3 @@
+# test tag macro expansion
+pass from { 127.0.0.1 127.0.0.2 127.0.0.3 } keep state tag "$srcaddr"
+pass from { 127.0.0.1 127.0.0.2 127.0.0.3 } keep state tagged "$srcaddr"
diff --git a/sbin/pfctl/tests/files/pf0085.ok b/sbin/pfctl/tests/files/pf0085.ok
new file mode 100644
index 000000000000..07e71ed5f70d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0085.ok
@@ -0,0 +1,6 @@
+pass inet from 127.0.0.1 to any flags S/SA keep state tag 127.0.0.1
+pass inet from 127.0.0.2 to any flags S/SA keep state tag 127.0.0.2
+pass inet from 127.0.0.3 to any flags S/SA keep state tag 127.0.0.3
+pass inet from 127.0.0.1 to any flags S/SA keep state tagged 127.0.0.1
+pass inet from 127.0.0.2 to any flags S/SA keep state tagged 127.0.0.2
+pass inet from 127.0.0.3 to any flags S/SA keep state tagged 127.0.0.3
diff --git a/sbin/pfctl/tests/files/pf0087.in b/sbin/pfctl/tests/files/pf0087.in
new file mode 100644
index 000000000000..cd19262b83e4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0087.in
@@ -0,0 +1,24 @@
+# pfctl -o rule reordering
+
+pass in on lo1000000 proto tcp from any to 10.0.0.2 port 22 keep state
+pass in on lo1000001 proto tcp from 10.0.0.1 port 22 to 10.0.0.2 keep state
+pass in on lo1000001 proto udp from 10.0.0.5 to 10.0.0.4 port 53 keep state
+pass in on lo1000000 proto udp from any to 10.0.0.2 port 53 keep state
+pass in proto tcp to 10.0.0.1 port 80 keep state
+pass out on lo1000001 proto udp from any to 10.0.0.2 port 53 keep state
+pass in proto tcp to 10.0.0.3 port 80 keep state
+pass out proto tcp to 10.0.0.1 port 81 keep state
+pass in proto udp to 10.0.0.3 port 53 keep state
+pass in on lo1000001 proto udp from 10.0.0.2 port 53 to 10.0.0.2 keep state
+pass out proto udp to 10.0.0.1 port 53 keep state
+pass out on lo1000000 proto udp from any to 10.0.0.2 port 53 keep state
+pass out proto udp to 10.0.0.3 port 53 keep state
+pass out on lo1000000 proto tcp from any to 10.0.0.2 port 22 keep state
+pass in on lo1000001 proto tcp from any to 10.0.0.2 port 22 keep state
+pass in on lo1000001 proto udp from any to 10.0.0.2 port 53 keep state
+pass in on lo1000001 proto tcp from 10.0.0.1 to 10.0.0.4 keep state
+pass out on lo1000001 proto tcp from any to 10.0.0.2 port 22 keep state
+pass out proto tcp to 10.0.0.1 port 80 keep state
+pass in proto udp to 10.0.0.1 port 53 keep state
+pass in on lo1000001 proto tcp from 10.0.0.1 to 10.0.0.6 port 22 keep state
+pass in on lo1000001 proto udp from 10.0.0.5 to 10.0.0.2 keep state
diff --git a/sbin/pfctl/tests/files/pf0087.ok b/sbin/pfctl/tests/files/pf0087.ok
new file mode 100644
index 000000000000..7aa69adefae0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0087.ok
@@ -0,0 +1,22 @@
+pass in on lo1000000 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state
+pass in on lo1000001 inet proto tcp from 10.0.0.1 port = ssh to 10.0.0.2 flags S/SA keep state
+pass in on lo1000001 inet proto udp from 10.0.0.5 to 10.0.0.4 port = domain keep state
+pass in on lo1000000 inet proto udp from any to 10.0.0.2 port = domain keep state
+pass in inet proto tcp from any to 10.0.0.1 port = http flags S/SA keep state
+pass out on lo1000001 inet proto udp from any to 10.0.0.2 port = domain keep state
+pass in inet proto tcp from any to 10.0.0.3 port = http flags S/SA keep state
+pass out inet proto tcp from any to 10.0.0.1 port = 81 flags S/SA keep state
+pass in inet proto udp from any to 10.0.0.3 port = domain keep state
+pass in on lo1000001 inet proto udp from 10.0.0.2 port = domain to 10.0.0.2 keep state
+pass out inet proto udp from any to 10.0.0.1 port = domain keep state
+pass out on lo1000000 inet proto udp from any to 10.0.0.2 port = domain keep state
+pass out inet proto udp from any to 10.0.0.3 port = domain keep state
+pass out on lo1000000 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state
+pass in on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state
+pass in on lo1000001 inet proto udp from any to 10.0.0.2 port = domain keep state
+pass in on lo1000001 inet proto tcp from 10.0.0.1 to 10.0.0.4 flags S/SA keep state
+pass out on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state
+pass out inet proto tcp from any to 10.0.0.1 port = http flags S/SA keep state
+pass in inet proto udp from any to 10.0.0.1 port = domain keep state
+pass in on lo1000001 inet proto tcp from 10.0.0.1 to 10.0.0.6 port = ssh flags S/SA keep state
+pass in on lo1000001 inet proto udp from 10.0.0.5 to 10.0.0.2 keep state
diff --git a/sbin/pfctl/tests/files/pf0088.in b/sbin/pfctl/tests/files/pf0088.in
new file mode 100644
index 000000000000..a85aa84a30bb
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0088.in
@@ -0,0 +1,32 @@
+# pfctl -o duplicate rules
+
+pass in on lo1000000 from any to 10.0.0.1
+pass in on lo1000000 inet from any to 10.0.0.1
+
+pass
+pass out
+pass out
+pass out quick
+
+pass on lo1000001 to 10.0.0.1
+pass on lo1000000 from any to 10.0.0.1
+
+pass to 10.0.0.2 modulate state
+pass to 10.0.0.2 keep state
+block from 10.0.0.3 to 10.0.0.2
+pass to 10.0.0.2 modulate state
+block from 10.0.0.3 to 10.0.0.2
+pass in to 10.0.0.2 synproxy state
+
+
+pass out proto tcp from 10.0.0.4 to 10.0.0.5 keep state
+pass out proto tcp from 10.0.0.4 to 10.0.0.5 port 80 keep state
+
+pass out
+pass in
+
+pass in on lo1000001 from any to any
+pass in on lo1000001 from any to any keep state
+pass in on lo1000001 from any to any
+
+block
diff --git a/sbin/pfctl/tests/files/pf0088.ok b/sbin/pfctl/tests/files/pf0088.ok
new file mode 100644
index 000000000000..801056a4ab46
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0088.ok
@@ -0,0 +1,22 @@
+pass in on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state
+pass in on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state
+pass all flags S/SA keep state
+pass out all flags S/SA keep state
+pass out all flags S/SA keep state
+pass out quick all flags S/SA keep state
+pass on lo1000001 inet from any to 10.0.0.1 flags S/SA keep state
+pass on lo1000000 inet from any to 10.0.0.1 flags S/SA keep state
+pass inet from any to 10.0.0.2 flags S/SA modulate state
+pass inet from any to 10.0.0.2 flags S/SA keep state
+block drop inet from 10.0.0.3 to 10.0.0.2
+pass inet from any to 10.0.0.2 flags S/SA modulate state
+block drop inet from 10.0.0.3 to 10.0.0.2
+pass in inet from any to 10.0.0.2 flags S/SA synproxy state
+pass out inet proto tcp from 10.0.0.4 to 10.0.0.5 flags S/SA keep state
+pass out inet proto tcp from 10.0.0.4 to 10.0.0.5 port = http flags S/SA keep state
+pass out all flags S/SA keep state
+pass in all flags S/SA keep state
+pass in on lo1000001 all flags S/SA keep state
+pass in on lo1000001 all flags S/SA keep state
+pass in on lo1000001 all flags S/SA keep state
+block drop all
diff --git a/sbin/pfctl/tests/files/pf0089.in b/sbin/pfctl/tests/files/pf0089.in
new file mode 100644
index 000000000000..1beda48b43b2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0089.in
@@ -0,0 +1,25 @@
+# TCP connection tracking
+
+table <bad> persist
+
+block all
+block quick from <bad>
+
+pass out proto tcp flags S/SA keep state
+pass out proto { icmp, udp } keep state
+
+pass in on lo1000001 proto tcp to 10.0.0.1 port 22 flags S/SA \
+ keep state (max-src-conn 10, max-src-conn-rate 3/99)
+
+pass in on lo1000001 proto tcp to 10.0.0.2 port 22 flags S/SA keep state \
+ (max-src-conn 10)
+
+pass in on lo1000001 proto tcp to 10.0.0.3 port 22 flags S/SA keep state \
+ (max-src-conn-rate 3/99)
+
+pass in on lo1000000 proto tcp to 10.0.0.1 port 80 flags S/SA modulate state \
+ (max-src-conn 100, max-src-conn-rate 10/5, overload <bad> flush)
+
+pass in on lo1000000 proto tcp to 10.0.0.1 port 8080 flags S/SA synproxy state \
+ (max-src-conn 1000, max-src-conn-rate 1000/5, overload <bad> \
+ flush global)
diff --git a/sbin/pfctl/tests/files/pf0089.ok b/sbin/pfctl/tests/files/pf0089.ok
new file mode 100644
index 000000000000..c2403e775da1
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0089.ok
@@ -0,0 +1,11 @@
+table <bad> persist
+block drop all
+block drop quick from <bad> to any
+pass out proto tcp all flags S/SA keep state
+pass out proto icmp all keep state
+pass out proto udp all keep state
+pass in on lo1000001 inet proto tcp from any to 10.0.0.1 port = ssh flags S/SA keep state (source-track rule, max-src-conn 10, max-src-conn-rate 3/99, src.track 99)
+pass in on lo1000001 inet proto tcp from any to 10.0.0.2 port = ssh flags S/SA keep state (source-track rule, max-src-conn 10)
+pass in on lo1000001 inet proto tcp from any to 10.0.0.3 port = ssh flags S/SA keep state (source-track rule, max-src-conn-rate 3/99, src.track 99)
+pass in on lo1000000 inet proto tcp from any to 10.0.0.1 port = http flags S/SA modulate state (source-track rule, max-src-conn 100, max-src-conn-rate 10/5, overload <bad> flush, src.track 5)
+pass in on lo1000000 inet proto tcp from any to 10.0.0.1 port = http-alt flags S/SA synproxy state (source-track rule, max-src-conn 1000, max-src-conn-rate 1000/5, overload <bad> flush global, src.track 5)
diff --git a/sbin/pfctl/tests/files/pf0090.in b/sbin/pfctl/tests/files/pf0090.in
new file mode 100644
index 000000000000..593ddc6a32ee
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0090.in
@@ -0,0 +1,5 @@
+pass log (user)
+pass log (all)
+pass log (to pflog7)
+block log (all, user, to pflog1)
+block log (to pflog1, user)
diff --git a/sbin/pfctl/tests/files/pf0090.ok b/sbin/pfctl/tests/files/pf0090.ok
new file mode 100644
index 000000000000..4255dc356c43
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0090.ok
@@ -0,0 +1,5 @@
+pass log (user) all flags S/SA keep state
+pass log (all) all flags S/SA keep state
+pass log (to pflog7) all flags S/SA keep state
+block drop log (all, user, to pflog1) all
+block drop log (user, to pflog1) all
diff --git a/sbin/pfctl/tests/files/pf0091.in b/sbin/pfctl/tests/files/pf0091.in
new file mode 100644
index 000000000000..b4fc631423e7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0091.in
@@ -0,0 +1,11 @@
+# basic anchor test
+anchor on tun1000000 {
+ anchor foo out {
+ pass proto tcp to port 1234
+ anchor proto tcp to port 2413 user root label "foo" {
+ block
+ pass from 127.0.0.1
+ }
+ }
+ pass in proto tcp to port 1234
+}
diff --git a/sbin/pfctl/tests/files/pf0091.ok b/sbin/pfctl/tests/files/pf0091.ok
new file mode 100644
index 000000000000..9f69e272d7fd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0091.ok
@@ -0,0 +1,10 @@
+anchor on tun1000000 all {
+ anchor "foo" out all {
+ pass proto tcp from any to any port = 1234 flags S/SA keep state
+ anchor proto tcp from any to any port = 2413 user = 0 label "foo" {
+ block drop all
+ pass inet from 127.0.0.1 to any flags S/SA keep state
+ }
+ }
+ pass in proto tcp from any to any port = 1234 flags S/SA keep state
+}
diff --git a/sbin/pfctl/tests/files/pf0092.in b/sbin/pfctl/tests/files/pf0092.in
new file mode 100644
index 000000000000..3af6ea6e38d3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0092.in
@@ -0,0 +1,30 @@
+anchor { # testing comments
+ anchor in {
+ # comment before rule
+ pass quick
+ }
+ # silly nesting
+ anchor out {
+ anchor in {
+ anchor out {
+ anchor in {
+ anchor out {
+ anchor in {
+ anchor out {
+ anchor in {
+ pass
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ pass in on tun1000000
+ anchor foo on tun1000000 {
+
+ pass
+ }
+} # comment after closing brace
+
diff --git a/sbin/pfctl/tests/files/pf0092.ok b/sbin/pfctl/tests/files/pf0092.ok
new file mode 100644
index 000000000000..43720c1afa2a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0092.ok
@@ -0,0 +1,26 @@
+anchor all {
+ anchor in all {
+ pass quick all flags S/SA keep state
+ }
+ anchor out all {
+ anchor in all {
+ anchor out all {
+ anchor in all {
+ anchor out all {
+ anchor in all {
+ anchor out all {
+ anchor in all {
+ pass all flags S/SA keep state
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ pass in on tun1000000 all flags S/SA keep state
+ anchor "foo" on tun1000000 all {
+ pass all flags S/SA keep state
+ }
+}
diff --git a/sbin/pfctl/tests/files/pf0094.in b/sbin/pfctl/tests/files/pf0094.in
new file mode 100644
index 000000000000..b0e3d0feebf8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0094.in
@@ -0,0 +1,4 @@
+pass from 10.1.2.3 - 10.1.2.4 to 10.2.3.4 - 10.3.4.5
+pass from 0.0.0.0 - 255.255.255.255
+pass from 2001:6f8:1098::2 - 2001:6f8:1098::5 to 2001:6f8:1098::3 - 2001:6f8:1098::4
+pass from ::0 - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
diff --git a/sbin/pfctl/tests/files/pf0094.ok b/sbin/pfctl/tests/files/pf0094.ok
new file mode 100644
index 000000000000..5a792644defd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0094.ok
@@ -0,0 +1,4 @@
+pass inet from 10.1.2.3 - 10.1.2.4 to 10.2.3.4 - 10.3.4.5 flags S/SA keep state
+pass inet from 0.0.0.0 - 255.255.255.255 to any flags S/SA keep state
+pass inet6 from 2001:6f8:1098::2 - 2001:6f8:1098::5 to 2001:6f8:1098::3 - 2001:6f8:1098::4 flags S/SA keep state
+pass inet6 from :: - ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0095.in b/sbin/pfctl/tests/files/pf0095.in
new file mode 100644
index 000000000000..c43914bc0017
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0095.in
@@ -0,0 +1,4 @@
+
+include "./pf0095.include"
+
+block out proto tcp
diff --git a/sbin/pfctl/tests/files/pf0095.include b/sbin/pfctl/tests/files/pf0095.include
new file mode 100644
index 000000000000..f852a7169cfe
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0095.include
@@ -0,0 +1,2 @@
+
+block in proto udp
diff --git a/sbin/pfctl/tests/files/pf0095.ok b/sbin/pfctl/tests/files/pf0095.ok
new file mode 100644
index 000000000000..004e1787865d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0095.ok
@@ -0,0 +1,2 @@
+block drop in proto udp all
+block drop out proto tcp all
diff --git a/sbin/pfctl/tests/files/pf0096.in b/sbin/pfctl/tests/files/pf0096.in
new file mode 100644
index 000000000000..4d1aed38e5bc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0096.in
@@ -0,0 +1,5 @@
+# varset allows concatenated strings as numbers
+myports = 5555 6666
+# and also can be used within another macro
+moreports = $myports 7777
+pass in proto tcp from any to any port { $moreports }
diff --git a/sbin/pfctl/tests/files/pf0096.ok b/sbin/pfctl/tests/files/pf0096.ok
new file mode 100644
index 000000000000..df7af0a3a157
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0096.ok
@@ -0,0 +1,5 @@
+myports = "5555 6666"
+moreports = "5555 6666 7777"
+pass in proto tcp from any to any port = personal-agent flags S/SA keep state
+pass in proto tcp from any to any port = 6666 flags S/SA keep state
+pass in proto tcp from any to any port = 7777 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0097.in b/sbin/pfctl/tests/files/pf0097.in
new file mode 100644
index 000000000000..b3fd4939b0a6
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0097.in
@@ -0,0 +1,4 @@
+pass in on em0 inet proto tcp from any to any port 220:230 divert-to 127.0.0.1 port 22
+#pass out on em0 inet proto tcp from any to any port 220:230 divert-reply
+pass on em0 inet proto tcp from any to any port 80 divert-to 127.0.0.1 port 8080
+pass in on em0 inet proto 103 divert-to 127.0.0.1 port 103 # FIXME
diff --git a/sbin/pfctl/tests/files/pf0097.ok b/sbin/pfctl/tests/files/pf0097.ok
new file mode 100644
index 000000000000..0a78066a9c25
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0097.ok
@@ -0,0 +1,3 @@
+pass in on em0 inet proto tcp from any to any port 220:230 flags S/SA keep state divert-to 22
+pass on em0 inet proto tcp from any to any port = http flags S/SA keep state divert-to 8080
+pass in on em0 inet proto pim all keep state divert-to 103
diff --git a/sbin/pfctl/tests/files/pf0098.in b/sbin/pfctl/tests/files/pf0098.in
new file mode 100644
index 000000000000..c26f0fcfe4d3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0098.in
@@ -0,0 +1,3 @@
+# Test rule order processing should pass (require-order no longer required)
+pass in on lo1000000 all
+match out on lo0 inet6 all nat-to lo0
diff --git a/sbin/pfctl/tests/files/pf0098.ok b/sbin/pfctl/tests/files/pf0098.ok
new file mode 100644
index 000000000000..105bb46b4ae5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0098.ok
@@ -0,0 +1,2 @@
+pass in on lo1000000 all flags S/SA keep state
+match out on lo0 inet6 all nat-to { ::1, fe80::1 } round-robin
diff --git a/sbin/pfctl/tests/files/pf0100.in b/sbin/pfctl/tests/files/pf0100.in
new file mode 100644
index 000000000000..287e1c9e4d7c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0100.in
@@ -0,0 +1,20 @@
+pass
+anchor "a/b"
+anchor "1/2/3" # test anchors with multiple path components
+anchor "relative" {
+ pass in on lo0 label TEST1
+}
+anchor "camield/*" # empty wildcard anchor
+
+anchor "relayd/*"
+
+anchor "foo" in on lo0 {
+ anchor "bar" in { # nested named inlined anchor
+ anchor "/1/2/3" # absolute multicomponent path
+ anchor "/relative" # absolute path
+ pass in on lo0 label FOO
+ }
+ anchor in { # nested unnamed inlined anchor
+ pass in on lo0 label BAR
+ }
+}
diff --git a/sbin/pfctl/tests/files/pf0100.ok b/sbin/pfctl/tests/files/pf0100.ok
new file mode 100644
index 000000000000..9f4427379bc7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0100.ok
@@ -0,0 +1,18 @@
+pass all flags S/SA keep state
+anchor "a/b" all
+anchor "1/2/3" all
+anchor "relative" all {
+ pass in on lo0 all flags S/SA keep state label "TEST1"
+}
+anchor "camield/*" all
+anchor "relayd/*" all
+anchor "foo" in on lo0 all {
+ anchor "bar" in all {
+ anchor "/1/2/3" all
+ anchor "/relative" all
+ pass in on lo0 all flags S/SA keep state label "FOO"
+ }
+ anchor in all {
+ pass in on lo0 all flags S/SA keep state label "BAR"
+ }
+}
diff --git a/sbin/pfctl/tests/files/pf0101.in b/sbin/pfctl/tests/files/pf0101.in
new file mode 100644
index 000000000000..8bf9dc6cb8da
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0101.in
@@ -0,0 +1,8 @@
+# test prio
+
+pass set prio 3
+
+pass out on lo1000000 proto tcp from any to any port 22 set prio (5 2)
+
+pass proto udp from any to { 127.0.0.1 127.0.0.2 } port 53 set prio 4
+
diff --git a/sbin/pfctl/tests/files/pf0101.ok b/sbin/pfctl/tests/files/pf0101.ok
new file mode 100644
index 000000000000..a46f2699711a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0101.ok
@@ -0,0 +1,4 @@
+pass all flags S/SA set ( prio 3 ) keep state
+pass out on lo1000000 proto tcp from any to any port = ssh flags S/SA set ( prio(5, 2) ) keep state
+pass inet proto udp from any to 127.0.0.1 port = domain set ( prio 4 ) keep state
+pass inet proto udp from any to 127.0.0.2 port = domain set ( prio 4 ) keep state
diff --git a/sbin/pfctl/tests/files/pf0102.in b/sbin/pfctl/tests/files/pf0102.in
new file mode 100644
index 000000000000..d0c3a1110482
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0102.in
@@ -0,0 +1,9 @@
+# test rule expansion with mixed af
+
+pass from {1.1.1.1 2002::} to (self)
+
+pass from {2002:: 1.1.1.1} to (self)
+
+pass from {1.1.1.1 2002::} to (self)/40
+
+pass from {2002:: 1.1.1.1} to (self)/40
diff --git a/sbin/pfctl/tests/files/pf0102.ok b/sbin/pfctl/tests/files/pf0102.ok
new file mode 100644
index 000000000000..1c76ec2725ba
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0102.ok
@@ -0,0 +1,8 @@
+pass inet from 1.1.1.1 to (self) flags S/SA keep state
+pass inet6 from 2002:: to (self) flags S/SA keep state
+pass inet6 from 2002:: to (self) flags S/SA keep state
+pass inet from 1.1.1.1 to (self) flags S/SA keep state
+pass inet from 1.1.1.1 to (self) flags S/SA keep state
+pass inet6 from 2002:: to (self)/40 flags S/SA keep state
+pass inet6 from 2002:: to (self)/40 flags S/SA keep state
+pass inet from 1.1.1.1 to (self) flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf0104.in b/sbin/pfctl/tests/files/pf0104.in
new file mode 100644
index 000000000000..91bd43e3a4bb
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0104.in
@@ -0,0 +1,10 @@
+# This test assumes that localhost points to 127.0.0.1 first
+pass in proto tcp to port 25 divert-to localhost port 8025
+# Test IPv4 addresses
+pass in proto tcp to port 25 divert-to 127.0.0.1 port 8025
+pass in inet proto tcp to port 25 divert-to 127.0.0.1 port 8025
+pass in inet proto tcp to port 25 divert-to localhost port 8025
+# Test IPv6 addresses
+pass in proto tcp to port 25 divert-to ::1 port 8025
+pass in inet6 proto tcp to port 25 divert-to ::1 port 8025
+pass in inet6 proto tcp to port 25 divert-to localhost port 8025
diff --git a/sbin/pfctl/tests/files/pf0104.ok b/sbin/pfctl/tests/files/pf0104.ok
new file mode 100644
index 000000000000..a4260f9ac98e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf0104.ok
@@ -0,0 +1,7 @@
+pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in inet proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in inet proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in inet6 proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
+pass in inet6 proto tcp from any to any port = smtp flags S/SA keep state divert-to 8025
diff --git a/sbin/pfctl/tests/files/pf1001.in b/sbin/pfctl/tests/files/pf1001.in
new file mode 100644
index 000000000000..9007d63aeebd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1001.in
@@ -0,0 +1,2 @@
+binat on em0 inet6 from fc00::/64 to any -> fc00:0:0:1::/64
+binat on em0 inet6 from any to fc00:0:0:1::/64 -> fc00::/64
diff --git a/sbin/pfctl/tests/files/pf1001.ok b/sbin/pfctl/tests/files/pf1001.ok
new file mode 100644
index 000000000000..9007d63aeebd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1001.ok
@@ -0,0 +1,2 @@
+binat on em0 inet6 from fc00::/64 to any -> fc00:0:0:1::/64
+binat on em0 inet6 from any to fc00:0:0:1::/64 -> fc00::/64
diff --git a/sbin/pfctl/tests/files/pf1002.in b/sbin/pfctl/tests/files/pf1002.in
new file mode 100644
index 000000000000..3fdde81be7de
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1002.in
@@ -0,0 +1,6 @@
+set timeout interval 10
+set timeout sctp.first 11
+set timeout sctp.opening 12
+set timeout sctp.established 13
+set timeout sctp.closing 14
+set timeout sctp.closed 15
diff --git a/sbin/pfctl/tests/files/pf1002.ok b/sbin/pfctl/tests/files/pf1002.ok
new file mode 100644
index 000000000000..3fdde81be7de
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1002.ok
@@ -0,0 +1,6 @@
+set timeout interval 10
+set timeout sctp.first 11
+set timeout sctp.opening 12
+set timeout sctp.established 13
+set timeout sctp.closing 14
+set timeout sctp.closed 15
diff --git a/sbin/pfctl/tests/files/pf1003.in b/sbin/pfctl/tests/files/pf1003.in
new file mode 100644
index 000000000000..298b3df81b52
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1003.in
@@ -0,0 +1,3 @@
+altq on em0 cbq(default) bandwidth 100Kb queue qmain
+queue qmain priority 4
+pass on em0 queue qmain
diff --git a/sbin/pfctl/tests/files/pf1003.ok b/sbin/pfctl/tests/files/pf1003.ok
new file mode 100644
index 000000000000..afc9817e3b35
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1003.ok
@@ -0,0 +1,3 @@
+altq on em0 cbq( default ) bandwidth 100Kb tbrsize 1500 queue { qmain }
+queue qmain priority 4
+pass on em0 all flags S/SA keep state queue qmain
diff --git a/sbin/pfctl/tests/files/pf1004.in b/sbin/pfctl/tests/files/pf1004.in
new file mode 100644
index 000000000000..e8f26bef9e1a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1004.in
@@ -0,0 +1,6 @@
+altq on em0 cbq(default codel) bandwidth 20Mb queue qmain
+queue qmain { q1 q2 }
+queue q1 priority 1 bandwidth 60%
+queue q2 priority 2 bandwidth 40%
+pass on em0 queue q1
+block on em0 queue q2
diff --git a/sbin/pfctl/tests/files/pf1004.ok b/sbin/pfctl/tests/files/pf1004.ok
new file mode 100644
index 000000000000..b2e033c6e87d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1004.ok
@@ -0,0 +1,6 @@
+altq on em0 cbq( codel default ) bandwidth 20Mb tbrsize 12000 queue { qmain }
+queue qmain { q1 q2 }
+queue q1 bandwidth 60%
+queue q2 bandwidth 40% priority 2
+pass on em0 all flags S/SA keep state queue q1
+block drop on em0 all queue q2
diff --git a/sbin/pfctl/tests/files/pf1005.in b/sbin/pfctl/tests/files/pf1005.in
new file mode 100644
index 000000000000..72e5c8f2a87d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1005.in
@@ -0,0 +1,3 @@
+rdr on em0 proto tcp from any to any -> 1.1.1.1 port 2121
+pass out log quick on lo0 route-to (lo0 localhost) inet from any to any
+pass in log quick on lo0 route-to (lo0 localhost) inet6 from any to any
diff --git a/sbin/pfctl/tests/files/pf1005.ok b/sbin/pfctl/tests/files/pf1005.ok
new file mode 100644
index 000000000000..a1678f61d4ad
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1005.ok
@@ -0,0 +1,3 @@
+rdr on em0 inet proto tcp all -> 1.1.1.1 port 2121
+pass out log quick on lo0 route-to (lo0 127.0.0.1) inet all flags S/SA keep state
+pass in log quick on lo0 route-to (lo0 ::1) inet6 all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1006.in b/sbin/pfctl/tests/files/pf1006.in
new file mode 100644
index 000000000000..b50c16994cfc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1006.in
@@ -0,0 +1,2 @@
+altq on igb0 fairq bandwidth 1Gb queue { qLink }
+queue qLink fairq(default)
diff --git a/sbin/pfctl/tests/files/pf1006.ok b/sbin/pfctl/tests/files/pf1006.ok
new file mode 100644
index 000000000000..be44b765c2e9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1006.ok
@@ -0,0 +1,2 @@
+altq on igb0 fairq bandwidth 1Gb tbrsize 36000 queue { qLink }
+queue qLink fairq( default )
diff --git a/sbin/pfctl/tests/files/pf1007.in b/sbin/pfctl/tests/files/pf1007.in
new file mode 100644
index 000000000000..e08b38d7241a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1007.in
@@ -0,0 +1 @@
+ether block out on igb0 to ! 00:01:02:03:04:05
diff --git a/sbin/pfctl/tests/files/pf1007.ok b/sbin/pfctl/tests/files/pf1007.ok
new file mode 100644
index 000000000000..742b5308ec90
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1007.ok
@@ -0,0 +1 @@
+ether block out on igb0 to ! 00:01:02:03:04:05 l3 all
diff --git a/sbin/pfctl/tests/files/pf1008.in b/sbin/pfctl/tests/files/pf1008.in
new file mode 100644
index 000000000000..a9bd472a5070
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1008.in
@@ -0,0 +1 @@
+ether block out on igb0 to 00:01:02:03:04:05/24
diff --git a/sbin/pfctl/tests/files/pf1008.ok b/sbin/pfctl/tests/files/pf1008.ok
new file mode 100644
index 000000000000..646ef77c78dd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1008.ok
@@ -0,0 +1 @@
+ether block out on igb0 to 00:01:02:03:04:05/24 l3 all
diff --git a/sbin/pfctl/tests/files/pf1009.in b/sbin/pfctl/tests/files/pf1009.in
new file mode 100644
index 000000000000..833c9099837c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1009.in
@@ -0,0 +1 @@
+ether block out on igb0 to 00:01:02:03:04:05&ff:ff:ff:00:00:ff
diff --git a/sbin/pfctl/tests/files/pf1009.ok b/sbin/pfctl/tests/files/pf1009.ok
new file mode 100644
index 000000000000..3023f1337dd3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1009.ok
@@ -0,0 +1 @@
+ether block out on igb0 to 00:01:02:03:04:05&ff:ff:ff:00:00:ff l3 all
diff --git a/sbin/pfctl/tests/files/pf1010.in b/sbin/pfctl/tests/files/pf1010.in
new file mode 100644
index 000000000000..2baf4dc360af
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1010.in
@@ -0,0 +1,2 @@
+pass inet proto icmp icmp-type {unreach}
+pass in route-to (if0 127.0.0.1/8) sticky-address inet
diff --git a/sbin/pfctl/tests/files/pf1010.ok b/sbin/pfctl/tests/files/pf1010.ok
new file mode 100644
index 000000000000..b960dbfc50b8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1010.ok
@@ -0,0 +1,2 @@
+pass inet proto icmp all icmp-type unreach keep state
+pass in route-to (if0 127.0.0.0/8) sticky-address inet all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1011.in b/sbin/pfctl/tests/files/pf1011.in
new file mode 100644
index 000000000000..84f0e7204e40
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1011.in
@@ -0,0 +1 @@
+scrub fragment no reassemble
diff --git a/sbin/pfctl/tests/files/pf1011.ok b/sbin/pfctl/tests/files/pf1011.ok
new file mode 100644
index 000000000000..48572b371d8d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1011.ok
@@ -0,0 +1 @@
+scrub all fragment no reassemble
diff --git a/sbin/pfctl/tests/files/pf1012.in b/sbin/pfctl/tests/files/pf1012.in
new file mode 100644
index 000000000000..9083d1bf5396
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1012.in
@@ -0,0 +1 @@
+scrub
diff --git a/sbin/pfctl/tests/files/pf1012.ok b/sbin/pfctl/tests/files/pf1012.ok
new file mode 100644
index 000000000000..b7f1f454fb6a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1012.ok
@@ -0,0 +1 @@
+scrub all fragment reassemble
diff --git a/sbin/pfctl/tests/files/pf1013.in b/sbin/pfctl/tests/files/pf1013.in
new file mode 100644
index 000000000000..053804e1a35a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1013.in
@@ -0,0 +1 @@
+ether block out on igb0 ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1013.ok b/sbin/pfctl/tests/files/pf1013.ok
new file mode 100644
index 000000000000..7395f3fd6311
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1013.ok
@@ -0,0 +1 @@
+ether block out on igb0 l3 all ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1014.in b/sbin/pfctl/tests/files/pf1014.in
new file mode 100644
index 000000000000..8739034f1bda
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1014.in
@@ -0,0 +1 @@
+ether block out on igb0 label "test"
diff --git a/sbin/pfctl/tests/files/pf1014.ok b/sbin/pfctl/tests/files/pf1014.ok
new file mode 100644
index 000000000000..d0086cb25e54
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1014.ok
@@ -0,0 +1 @@
+ether block out on igb0 l3 all label "test"
diff --git a/sbin/pfctl/tests/files/pf1015.in b/sbin/pfctl/tests/files/pf1015.in
new file mode 100644
index 000000000000..11c7a211ae8a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1015.in
@@ -0,0 +1 @@
+ether block out on igb0 label "test" label "another label"
diff --git a/sbin/pfctl/tests/files/pf1015.ok b/sbin/pfctl/tests/files/pf1015.ok
new file mode 100644
index 000000000000..d3ea76f1875b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1015.ok
@@ -0,0 +1 @@
+ether block out on igb0 l3 all label "test" label "another label"
diff --git a/sbin/pfctl/tests/files/pf1016.in b/sbin/pfctl/tests/files/pf1016.in
new file mode 100644
index 000000000000..a7b1f6bc0ca9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1016.in
@@ -0,0 +1 @@
+ether block out on igb0 label "test" ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1016.ok b/sbin/pfctl/tests/files/pf1016.ok
new file mode 100644
index 000000000000..f1d59c988730
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1016.ok
@@ -0,0 +1 @@
+ether block out on igb0 l3 all label "test" ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1017.in b/sbin/pfctl/tests/files/pf1017.in
new file mode 100644
index 000000000000..ad523337bdc5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1017.in
@@ -0,0 +1 @@
+ether block out on igb0 label "test" label "another test" ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1017.ok b/sbin/pfctl/tests/files/pf1017.ok
new file mode 100644
index 000000000000..0efdd55e27a0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1017.ok
@@ -0,0 +1 @@
+ether block out on igb0 l3 all label "test" label "another test" ridentifier 12345678
diff --git a/sbin/pfctl/tests/files/pf1018.in b/sbin/pfctl/tests/files/pf1018.in
new file mode 100644
index 000000000000..90f0a3a0bab7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1018.in
@@ -0,0 +1 @@
+pass from { 192.0.2.1 2001:db8::1 } to (pppoe0)
diff --git a/sbin/pfctl/tests/files/pf1018.ok b/sbin/pfctl/tests/files/pf1018.ok
new file mode 100644
index 000000000000..04950f0035b8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1018.ok
@@ -0,0 +1,2 @@
+pass inet from 192.0.2.1 to (pppoe0) flags S/SA keep state
+pass inet6 from 2001:db8::1 to (pppoe0) flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1019.in b/sbin/pfctl/tests/files/pf1019.in
new file mode 100644
index 000000000000..04a770768714
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1019.in
@@ -0,0 +1 @@
+pass in keep state (pflow)
diff --git a/sbin/pfctl/tests/files/pf1019.ok b/sbin/pfctl/tests/files/pf1019.ok
new file mode 100644
index 000000000000..e865d57da16c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1019.ok
@@ -0,0 +1 @@
+pass in all flags S/SA keep state (pflow)
diff --git a/sbin/pfctl/tests/files/pf1020.in b/sbin/pfctl/tests/files/pf1020.in
new file mode 100644
index 000000000000..7f98df69bd04
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1020.in
@@ -0,0 +1,3 @@
+table <tabl1> file "./pf1020.include"
+
+block from <tabl1>
diff --git a/sbin/pfctl/tests/files/pf1020.include b/sbin/pfctl/tests/files/pf1020.include
new file mode 100644
index 000000000000..3fca07f64bfa
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1020.include
@@ -0,0 +1,4 @@
+; comment1
+# comment2
+1.0.0.1/32 ; comment1
+2.0.0.2/32 # comment2
diff --git a/sbin/pfctl/tests/files/pf1020.ok b/sbin/pfctl/tests/files/pf1020.ok
new file mode 100644
index 000000000000..16073b3d6987
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1020.ok
@@ -0,0 +1,2 @@
+table <tabl1> file "./pf1020.include"
+block drop from <tabl1> to any
diff --git a/sbin/pfctl/tests/files/pf1021.in b/sbin/pfctl/tests/files/pf1021.in
new file mode 100644
index 000000000000..841b024157c6
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1021.in
@@ -0,0 +1 @@
+nat on vtnet1 inet from ! (vtnet1) to any -> (vtnet1) endpoint-independent
diff --git a/sbin/pfctl/tests/files/pf1021.ok b/sbin/pfctl/tests/files/pf1021.ok
new file mode 100644
index 000000000000..3b5b84e2e11b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1021.ok
@@ -0,0 +1 @@
+nat on vtnet1 inet from ! (vtnet1) to any -> (vtnet1) round-robin endpoint-independent
diff --git a/sbin/pfctl/tests/files/pf1022.in b/sbin/pfctl/tests/files/pf1022.in
new file mode 100644
index 000000000000..640eb1334100
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1022.in
@@ -0,0 +1 @@
+pass out on em0 from 192.0.2.1 to 198.51.100.1 received-on fxp0
diff --git a/sbin/pfctl/tests/files/pf1022.ok b/sbin/pfctl/tests/files/pf1022.ok
new file mode 100644
index 000000000000..2f7b4a5bd616
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1022.ok
@@ -0,0 +1 @@
+pass out on em0 inet from 192.0.2.1 to 198.51.100.1 received-on fxp0 flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1023.in b/sbin/pfctl/tests/files/pf1023.in
new file mode 100644
index 000000000000..4855ae0f339e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1023.in
@@ -0,0 +1,3 @@
+match log(matches) inet proto tcp
+match log(matches) inet from 192.0.2.0/24
+pass
diff --git a/sbin/pfctl/tests/files/pf1023.ok b/sbin/pfctl/tests/files/pf1023.ok
new file mode 100644
index 000000000000..63fa40113ecf
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1023.ok
@@ -0,0 +1,3 @@
+match log (matches) inet proto tcp all
+match log (matches) inet from 192.0.2.0/24 to any
+pass all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1024.in b/sbin/pfctl/tests/files/pf1024.in
new file mode 100644
index 000000000000..be518bb3bd53
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1024.in
@@ -0,0 +1 @@
+pass in inet af-to inet6 from 2001:db8::1
diff --git a/sbin/pfctl/tests/files/pf1024.ok b/sbin/pfctl/tests/files/pf1024.ok
new file mode 100644
index 000000000000..2d4ddb9d0ce7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1024.ok
@@ -0,0 +1 @@
+pass in inet all flags S/SA keep state af-to inet6 from 2001:db8::1
diff --git a/sbin/pfctl/tests/files/pf1025.in b/sbin/pfctl/tests/files/pf1025.in
new file mode 100644
index 000000000000..d4ad821a6899
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1025.in
@@ -0,0 +1 @@
+pass in from 10.0.0.0/8 af-to inet6 from 2001:db8::1
diff --git a/sbin/pfctl/tests/files/pf1025.ok b/sbin/pfctl/tests/files/pf1025.ok
new file mode 100644
index 000000000000..8f48c987c6a0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1025.ok
@@ -0,0 +1 @@
+pass in inet from 10.0.0.0/8 to any flags S/SA keep state af-to inet6 from 2001:db8::1
diff --git a/sbin/pfctl/tests/files/pf1026.in b/sbin/pfctl/tests/files/pf1026.in
new file mode 100644
index 000000000000..3691d0947b39
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1026.in
@@ -0,0 +1 @@
+pass in on epair2b route-to (epair0a 192.0.2.2) inet6 from any to 64:ff9b::/96 af-to inet from (epair0a)
diff --git a/sbin/pfctl/tests/files/pf1026.ok b/sbin/pfctl/tests/files/pf1026.ok
new file mode 100644
index 000000000000..323036f2b800
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1026.ok
@@ -0,0 +1 @@
+pass in on epair2b route-to (epair0a 192.0.2.2) inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from (epair0a) round-robin
diff --git a/sbin/pfctl/tests/files/pf1027.in b/sbin/pfctl/tests/files/pf1027.in
new file mode 100644
index 000000000000..3c5c24025e0a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1027.in
@@ -0,0 +1 @@
+pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 af-to inet from (epair0a)
diff --git a/sbin/pfctl/tests/files/pf1027.ok b/sbin/pfctl/tests/files/pf1027.ok
new file mode 100644
index 000000000000..b50f1e216837
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1027.ok
@@ -0,0 +1 @@
+pass in on epair2b reply-to (epair0a 2001:db8::1) inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from (epair0a) round-robin
diff --git a/sbin/pfctl/tests/files/pf1028.in b/sbin/pfctl/tests/files/pf1028.in
new file mode 100644
index 000000000000..2386fcb52249
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1028.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1028.ok b/sbin/pfctl/tests/files/pf1028.ok
new file mode 100644
index 000000000000..07be890f4e05
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1028.ok
@@ -0,0 +1 @@
+rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1029.in b/sbin/pfctl/tests/files/pf1029.in
new file mode 100644
index 000000000000..73815839aadd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1029.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1002
diff --git a/sbin/pfctl/tests/files/pf1029.ok b/sbin/pfctl/tests/files/pf1029.ok
new file mode 100644
index 000000000000..6e9083bf856a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1029.ok
@@ -0,0 +1 @@
+rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1002
diff --git a/sbin/pfctl/tests/files/pf1030.in b/sbin/pfctl/tests/files/pf1030.in
new file mode 100644
index 000000000000..b6f891998a71
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1030.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535
diff --git a/sbin/pfctl/tests/files/pf1030.ok b/sbin/pfctl/tests/files/pf1030.ok
new file mode 100644
index 000000000000..4f6b2eba2f39
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1030.ok
@@ -0,0 +1 @@
+rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535
diff --git a/sbin/pfctl/tests/files/pf1031.in b/sbin/pfctl/tests/files/pf1031.in
new file mode 100644
index 000000000000..7cad4ae64000
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1031.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 port 1004:2004 -> 192.0.2.3 port 1004
diff --git a/sbin/pfctl/tests/files/pf1031.ok b/sbin/pfctl/tests/files/pf1031.ok
new file mode 100644
index 000000000000..8dd7fe027716
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1031.ok
@@ -0,0 +1 @@
+rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 port 1004:2004 -> 192.0.2.3 port 1004
diff --git a/sbin/pfctl/tests/files/pf1032.in b/sbin/pfctl/tests/files/pf1032.in
new file mode 100644
index 000000000000..a2eec78da045
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1032.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 port 1005:2005 -> 192.0.2.3 port 3004:*
diff --git a/sbin/pfctl/tests/files/pf1032.ok b/sbin/pfctl/tests/files/pf1032.ok
new file mode 100644
index 000000000000..3b3f124efc33
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1032.ok
@@ -0,0 +1 @@
+rdr on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 port 1005:2005 -> 192.0.2.3 port 3004:4004
diff --git a/sbin/pfctl/tests/files/pf1033.fail b/sbin/pfctl/tests/files/pf1033.fail
new file mode 100644
index 000000000000..d9fbfe4296e3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1033.fail
@@ -0,0 +1 @@
+the 'static-port' option is only valid with nat rules
diff --git a/sbin/pfctl/tests/files/pf1033.in b/sbin/pfctl/tests/files/pf1033.in
new file mode 100644
index 000000000000..76f33e7e8f0e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1033.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port
diff --git a/sbin/pfctl/tests/files/pf1034.fail b/sbin/pfctl/tests/files/pf1034.fail
new file mode 100644
index 000000000000..e407996a8fa3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1034.fail
@@ -0,0 +1 @@
+the 'map-e-portset' option is only valid with nat rules
diff --git a/sbin/pfctl/tests/files/pf1034.in b/sbin/pfctl/tests/files/pf1034.in
new file mode 100644
index 000000000000..be847a8af241
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1034.in
@@ -0,0 +1 @@
+rdr on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/0x34
diff --git a/sbin/pfctl/tests/files/pf1035.in b/sbin/pfctl/tests/files/pf1035.in
new file mode 100644
index 000000000000..9382ffedc8c9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1035.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1035.ok b/sbin/pfctl/tests/files/pf1035.ok
new file mode 100644
index 000000000000..be573ef460f5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1035.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1036.in b/sbin/pfctl/tests/files/pf1036.in
new file mode 100644
index 000000000000..81718c908303
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1036.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 50001:65535
diff --git a/sbin/pfctl/tests/files/pf1036.ok b/sbin/pfctl/tests/files/pf1036.ok
new file mode 100644
index 000000000000..be573ef460f5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1036.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1037.in b/sbin/pfctl/tests/files/pf1037.in
new file mode 100644
index 000000000000..a30f6c0e7bbe
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1037.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1003
diff --git a/sbin/pfctl/tests/files/pf1037.ok b/sbin/pfctl/tests/files/pf1037.ok
new file mode 100644
index 000000000000..020e2de28dec
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1037.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1003
diff --git a/sbin/pfctl/tests/files/pf1038.in b/sbin/pfctl/tests/files/pf1038.in
new file mode 100644
index 000000000000..532060e56494
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1038.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1004:2004
diff --git a/sbin/pfctl/tests/files/pf1038.ok b/sbin/pfctl/tests/files/pf1038.ok
new file mode 100644
index 000000000000..a4021db7b1b2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1038.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1004:2004
diff --git a/sbin/pfctl/tests/files/pf1039.in b/sbin/pfctl/tests/files/pf1039.in
new file mode 100644
index 000000000000..dba14b0625de
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1039.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port
diff --git a/sbin/pfctl/tests/files/pf1039.ok b/sbin/pfctl/tests/files/pf1039.ok
new file mode 100644
index 000000000000..80cfbe742865
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1039.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port
diff --git a/sbin/pfctl/tests/files/pf1040.fail b/sbin/pfctl/tests/files/pf1040.fail
new file mode 100644
index 000000000000..5b9afc22b441
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1040.fail
@@ -0,0 +1 @@
+the 'static-port' option can't be used when specifying a port range
diff --git a/sbin/pfctl/tests/files/pf1040.in b/sbin/pfctl/tests/files/pf1040.in
new file mode 100644
index 000000000000..38d7292a560a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1040.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1006 static-port
diff --git a/sbin/pfctl/tests/files/pf1040.ok b/sbin/pfctl/tests/files/pf1040.ok
new file mode 100644
index 000000000000..ffe2e023f77c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1040.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/52
diff --git a/sbin/pfctl/tests/files/pf1041.in b/sbin/pfctl/tests/files/pf1041.in
new file mode 100644
index 000000000000..4c384ac70e05
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1041.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/0x34
diff --git a/sbin/pfctl/tests/files/pf1041.ok b/sbin/pfctl/tests/files/pf1041.ok
new file mode 100644
index 000000000000..ffe2e023f77c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1041.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 map-e-portset 6/8/52
diff --git a/sbin/pfctl/tests/files/pf1042.fail b/sbin/pfctl/tests/files/pf1042.fail
new file mode 100644
index 000000000000..56e174a5ece5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1042.fail
@@ -0,0 +1 @@
+the 'map-e-portset' option can't be used 'static-port'
diff --git a/sbin/pfctl/tests/files/pf1042.in b/sbin/pfctl/tests/files/pf1042.in
new file mode 100644
index 000000000000..906f637b6a0a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1042.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 static-port map-e-portset 6/8/0x34
diff --git a/sbin/pfctl/tests/files/pf1043.fail b/sbin/pfctl/tests/files/pf1043.fail
new file mode 100644
index 000000000000..cdfab00916a2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1043.fail
@@ -0,0 +1 @@
+the 'map-e-portset' option can't be used when specifying a port range
diff --git a/sbin/pfctl/tests/files/pf1043.in b/sbin/pfctl/tests/files/pf1043.in
new file mode 100644
index 000000000000..15428a9e54bc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1043.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3 port 1007 map-e-portset 6/8/0x34
diff --git a/sbin/pfctl/tests/files/pf1044.in b/sbin/pfctl/tests/files/pf1044.in
new file mode 100644
index 000000000000..6a927b66b83f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1044.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> sticky-address
diff --git a/sbin/pfctl/tests/files/pf1044.ok b/sbin/pfctl/tests/files/pf1044.ok
new file mode 100644
index 000000000000..a68b1daaa73a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1044.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> round-robin sticky-address
diff --git a/sbin/pfctl/tests/files/pf1045.in b/sbin/pfctl/tests/files/pf1045.in
new file mode 100644
index 000000000000..38f708ce19b8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1045.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 bitmask
diff --git a/sbin/pfctl/tests/files/pf1045.ok b/sbin/pfctl/tests/files/pf1045.ok
new file mode 100644
index 000000000000..5388db7e58a4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1045.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 bitmask
diff --git a/sbin/pfctl/tests/files/pf1046.fail b/sbin/pfctl/tests/files/pf1046.fail
new file mode 100644
index 000000000000..b152f9063241
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1046.fail
@@ -0,0 +1 @@
+tables are not supported by pool type
diff --git a/sbin/pfctl/tests/files/pf1046.in b/sbin/pfctl/tests/files/pf1046.in
new file mode 100644
index 000000000000..e4a9f79efd6f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1046.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> bitmask
diff --git a/sbin/pfctl/tests/files/pf1047.fail b/sbin/pfctl/tests/files/pf1047.fail
new file mode 100644
index 000000000000..239b96b2fed4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1047.fail
@@ -0,0 +1 @@
+interface \(vtnet1\) is not supported by pool type
diff --git a/sbin/pfctl/tests/files/pf1047.in b/sbin/pfctl/tests/files/pf1047.in
new file mode 100644
index 000000000000..369bfcb0fb26
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1047.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> (vtnet1) bitmask
diff --git a/sbin/pfctl/tests/files/pf1048.in b/sbin/pfctl/tests/files/pf1048.in
new file mode 100644
index 000000000000..01232a33b5d8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1048.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 random
diff --git a/sbin/pfctl/tests/files/pf1048.ok b/sbin/pfctl/tests/files/pf1048.ok
new file mode 100644
index 000000000000..35e86fc676fc
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1048.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 random
diff --git a/sbin/pfctl/tests/files/pf1049.in b/sbin/pfctl/tests/files/pf1049.in
new file mode 100644
index 000000000000..3f2e5acf8265
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1049.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 192.0.2.3 }
diff --git a/sbin/pfctl/tests/files/pf1049.ok b/sbin/pfctl/tests/files/pf1049.ok
new file mode 100644
index 000000000000..be573ef460f5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1049.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 192.0.2.3
diff --git a/sbin/pfctl/tests/files/pf1050.in b/sbin/pfctl/tests/files/pf1050.in
new file mode 100644
index 000000000000..69ccaf445c3b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1050.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets>
diff --git a/sbin/pfctl/tests/files/pf1050.ok b/sbin/pfctl/tests/files/pf1050.ok
new file mode 100644
index 000000000000..24ca9b459bb7
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1050.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> <targets> round-robin
diff --git a/sbin/pfctl/tests/files/pf1051.in b/sbin/pfctl/tests/files/pf1051.in
new file mode 100644
index 000000000000..734da64a372c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1051.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 203.0.113.2 }
diff --git a/sbin/pfctl/tests/files/pf1051.ok b/sbin/pfctl/tests/files/pf1051.ok
new file mode 100644
index 000000000000..86f23488be41
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1051.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, 203.0.113.2 } round-robin
diff --git a/sbin/pfctl/tests/files/pf1052.in b/sbin/pfctl/tests/files/pf1052.in
new file mode 100644
index 000000000000..2ea770f3c06e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1052.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 <targets> }
diff --git a/sbin/pfctl/tests/files/pf1052.ok b/sbin/pfctl/tests/files/pf1052.ok
new file mode 100644
index 000000000000..b71d105eb77a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1052.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, <targets> } round-robin
diff --git a/sbin/pfctl/tests/files/pf1053.in b/sbin/pfctl/tests/files/pf1053.in
new file mode 100644
index 000000000000..f0cced0b64a2
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1053.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24
diff --git a/sbin/pfctl/tests/files/pf1053.ok b/sbin/pfctl/tests/files/pf1053.ok
new file mode 100644
index 000000000000..de321b8c738f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1053.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24
diff --git a/sbin/pfctl/tests/files/pf1054.in b/sbin/pfctl/tests/files/pf1054.in
new file mode 100644
index 000000000000..9e66bb2a81d6
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1054.in
@@ -0,0 +1,3 @@
+# XXX: it causes just the 0th address to be used without cycling
+# Probably a bug
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 round-robin
diff --git a/sbin/pfctl/tests/files/pf1054.ok b/sbin/pfctl/tests/files/pf1054.ok
new file mode 100644
index 000000000000..3d7ab7974d87
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1054.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 round-robin
diff --git a/sbin/pfctl/tests/files/pf1055.in b/sbin/pfctl/tests/files/pf1055.in
new file mode 100644
index 000000000000..c116ef5fd43e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1055.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 source-hash 0x42424242424242424242424242424242
diff --git a/sbin/pfctl/tests/files/pf1055.ok b/sbin/pfctl/tests/files/pf1055.ok
new file mode 100644
index 000000000000..468e47012169
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1055.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.0/24 source-hash 0x42424242424242424242424242424242
diff --git a/sbin/pfctl/tests/files/pf1056.in b/sbin/pfctl/tests/files/pf1056.in
new file mode 100644
index 000000000000..bd2af077fc3f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1056.in
@@ -0,0 +1 @@
+pass in on vtnet0 inet6 from any to 64:ff9b::/96 af-to inet from 203.0.113.1 to 203.0.113.2
diff --git a/sbin/pfctl/tests/files/pf1056.ok b/sbin/pfctl/tests/files/pf1056.ok
new file mode 100644
index 000000000000..0397570dbce0
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1056.ok
@@ -0,0 +1 @@
+pass in on vtnet0 inet6 from any to 64:ff9b::/96 flags S/SA keep state af-to inet from 203.0.113.1 to 203.0.113.2
diff --git a/sbin/pfctl/tests/files/pf1057.in b/sbin/pfctl/tests/files/pf1057.in
new file mode 100644
index 000000000000..0e26976e5a0d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1057.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> vlan1057
diff --git a/sbin/pfctl/tests/files/pf1057.ok b/sbin/pfctl/tests/files/pf1057.ok
new file mode 100644
index 000000000000..7626951e138c
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1057.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> 203.0.113.5
diff --git a/sbin/pfctl/tests/files/pf1058.in b/sbin/pfctl/tests/files/pf1058.in
new file mode 100644
index 000000000000..27c0ef1d69b3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1058.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1 vlan1058 }
diff --git a/sbin/pfctl/tests/files/pf1058.ok b/sbin/pfctl/tests/files/pf1058.ok
new file mode 100644
index 000000000000..b1d2b07a58b4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1058.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.1, 203.0.113.5 } round-robin
diff --git a/sbin/pfctl/tests/files/pf1059.in b/sbin/pfctl/tests/files/pf1059.in
new file mode 100644
index 000000000000..92ed5c50656b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1059.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> (vlan1059)
diff --git a/sbin/pfctl/tests/files/pf1059.ok b/sbin/pfctl/tests/files/pf1059.ok
new file mode 100644
index 000000000000..6b028f18ee60
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1059.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> (vlan1059) round-robin
diff --git a/sbin/pfctl/tests/files/pf1060.in b/sbin/pfctl/tests/files/pf1060.in
new file mode 100644
index 000000000000..85cdd19f2897
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1060.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.0 (vlan1060) }
diff --git a/sbin/pfctl/tests/files/pf1060.ok b/sbin/pfctl/tests/files/pf1060.ok
new file mode 100644
index 000000000000..3364b3cbdcc5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1060.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet proto tcp from 192.0.2.1 to 192.0.2.2 -> { 203.0.113.0, (vlan1060) } round-robin
diff --git a/sbin/pfctl/tests/files/pf1061.in b/sbin/pfctl/tests/files/pf1061.in
new file mode 100644
index 000000000000..32eb8272db8b
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1061.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> vlan1061:0
diff --git a/sbin/pfctl/tests/files/pf1061.ok b/sbin/pfctl/tests/files/pf1061.ok
new file mode 100644
index 000000000000..d2e6d969cb11
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1061.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> 2001:db8::cb00:7105
diff --git a/sbin/pfctl/tests/files/pf1062.in b/sbin/pfctl/tests/files/pf1062.in
new file mode 100644
index 000000000000..4d6a0ecc2e92
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1062.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> { 2001:db8::3 vlan1062:0 }
diff --git a/sbin/pfctl/tests/files/pf1062.ok b/sbin/pfctl/tests/files/pf1062.ok
new file mode 100644
index 000000000000..cb5db62ded1d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1062.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> { 2001:db8::3, 2001:db8::cb00:7105 } round-robin
diff --git a/sbin/pfctl/tests/files/pf1063.in b/sbin/pfctl/tests/files/pf1063.in
new file mode 100644
index 000000000000..3d164538640d
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1063.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> (vlan1063)
diff --git a/sbin/pfctl/tests/files/pf1063.ok b/sbin/pfctl/tests/files/pf1063.ok
new file mode 100644
index 000000000000..13189e00cc8a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1063.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> (vlan1063) round-robin
diff --git a/sbin/pfctl/tests/files/pf1064.in b/sbin/pfctl/tests/files/pf1064.in
new file mode 100644
index 000000000000..78d04135154f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1064.in
@@ -0,0 +1 @@
+nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2 -> { fe80::2 (vlan1064) }
diff --git a/sbin/pfctl/tests/files/pf1064.ok b/sbin/pfctl/tests/files/pf1064.ok
new file mode 100644
index 000000000000..ed15d054ab34
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1064.ok
@@ -0,0 +1 @@
+nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2 -> { fe80::2, (vlan1064) } round-robin
diff --git a/sbin/pfctl/tests/files/pf1065.in b/sbin/pfctl/tests/files/pf1065.in
new file mode 100644
index 000000000000..690045befee6
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1065.in
@@ -0,0 +1 @@
+no nat on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2
diff --git a/sbin/pfctl/tests/files/pf1065.ok b/sbin/pfctl/tests/files/pf1065.ok
new file mode 100644
index 000000000000..651a2fa0ae09
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1065.ok
@@ -0,0 +1 @@
+no nat on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2
diff --git a/sbin/pfctl/tests/files/pf1066.in b/sbin/pfctl/tests/files/pf1066.in
new file mode 100644
index 000000000000..e81461c470ab
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1066.in
@@ -0,0 +1 @@
+no rdr on vtnet0 proto tcp from 2001:db8::1 to 2001:db8::2
diff --git a/sbin/pfctl/tests/files/pf1066.ok b/sbin/pfctl/tests/files/pf1066.ok
new file mode 100644
index 000000000000..5ff596fa0158
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1066.ok
@@ -0,0 +1 @@
+no rdr on vtnet0 inet6 proto tcp from 2001:db8::1 to 2001:db8::2
diff --git a/sbin/pfctl/tests/files/pf1067.fail b/sbin/pfctl/tests/files/pf1067.fail
new file mode 100644
index 000000000000..23ac1daad64f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1067.fail
@@ -0,0 +1 @@
+route-to, reply-to and dup-to are not supported on block rules
diff --git a/sbin/pfctl/tests/files/pf1067.in b/sbin/pfctl/tests/files/pf1067.in
new file mode 100644
index 000000000000..47f3bf6285dd
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1067.in
@@ -0,0 +1 @@
+block in route-to (if0 127.0.0.1/8)
diff --git a/sbin/pfctl/tests/files/pf1068.in b/sbin/pfctl/tests/files/pf1068.in
new file mode 100644
index 000000000000..993cfa37f8f9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1068.in
@@ -0,0 +1 @@
+pass in proto icmp max-pkt-rate 100/10
diff --git a/sbin/pfctl/tests/files/pf1068.ok b/sbin/pfctl/tests/files/pf1068.ok
new file mode 100644
index 000000000000..bd36043207f9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1068.ok
@@ -0,0 +1 @@
+pass in proto icmp all max-pkt-rate 100/10 keep state
diff --git a/sbin/pfctl/tests/files/pf1069.in b/sbin/pfctl/tests/files/pf1069.in
new file mode 100644
index 000000000000..3a69158fff7e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1069.in
@@ -0,0 +1 @@
+pass in proto icmp max-pkt-size 128
diff --git a/sbin/pfctl/tests/files/pf1069.ok b/sbin/pfctl/tests/files/pf1069.ok
new file mode 100644
index 000000000000..b79228266156
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1069.ok
@@ -0,0 +1 @@
+pass in proto icmp all max-pkt-size 128 keep state
diff --git a/sbin/pfctl/tests/files/pf1070.fail b/sbin/pfctl/tests/files/pf1070.fail
new file mode 100644
index 000000000000..60b56d9da2b9
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1070.fail
@@ -0,0 +1 @@
+pf1070.include:2: syntax error
diff --git a/sbin/pfctl/tests/files/pf1070.in b/sbin/pfctl/tests/files/pf1070.in
new file mode 100644
index 000000000000..42b874d4d6f4
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1070.in
@@ -0,0 +1,2 @@
+pass in
+include pf1070.include
diff --git a/sbin/pfctl/tests/files/pf1070.include b/sbin/pfctl/tests/files/pf1070.include
new file mode 100644
index 000000000000..09c3755dbe28
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1070.include
@@ -0,0 +1,2 @@
+block out
+invalidline
diff --git a/sbin/pfctl/tests/files/pf1071.in b/sbin/pfctl/tests/files/pf1071.in
new file mode 100644
index 000000000000..9e6c2abc0621
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1071.in
@@ -0,0 +1 @@
+pass inet from (lo0)/24
diff --git a/sbin/pfctl/tests/files/pf1071.ok b/sbin/pfctl/tests/files/pf1071.ok
new file mode 100644
index 000000000000..409b5dc4b068
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1071.ok
@@ -0,0 +1 @@
+pass inet from (lo0)/24 to any flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1072.fail b/sbin/pfctl/tests/files/pf1072.fail
new file mode 100644
index 000000000000..06ef5ae457e5
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1072.fail
@@ -0,0 +1 @@
+invalid port range
diff --git a/sbin/pfctl/tests/files/pf1072.in b/sbin/pfctl/tests/files/pf1072.in
new file mode 100644
index 000000000000..e09e92388ce1
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1072.in
@@ -0,0 +1 @@
+pass in proto tcp from any port 500:100 to any
diff --git a/sbin/pfctl/tests/files/pf1073.in b/sbin/pfctl/tests/files/pf1073.in
new file mode 100644
index 000000000000..477995893ac3
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1073.in
@@ -0,0 +1 @@
+pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) prefer-ipv6-nexthop inet
diff --git a/sbin/pfctl/tests/files/pf1073.ok b/sbin/pfctl/tests/files/pf1073.ok
new file mode 100644
index 000000000000..f34867508c75
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1073.ok
@@ -0,0 +1 @@
+pass in on vtnet0 route-to (vtnet1 2001:db8::1) prefer-ipv6-nexthop inet all flags S/SA keep state
diff --git a/sbin/pfctl/tests/files/pf1074.fail b/sbin/pfctl/tests/files/pf1074.fail
new file mode 100644
index 000000000000..afe8ee3c458f
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1074.fail
@@ -0,0 +1 @@
+no routing address with matching address family found.
diff --git a/sbin/pfctl/tests/files/pf1074.in b/sbin/pfctl/tests/files/pf1074.in
new file mode 100644
index 000000000000..5d285bc5d6e8
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1074.in
@@ -0,0 +1 @@
+pass in on vtnet0 route-to ( vtnet1 2001:db8::1 ) inet
diff --git a/sbin/pfctl/tests/files/pf1075.in b/sbin/pfctl/tests/files/pf1075.in
new file mode 100644
index 000000000000..835a31a25c6a
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1075.in
@@ -0,0 +1 @@
+pass inet from (lo0)/24 once
diff --git a/sbin/pfctl/tests/files/pf1075.ok b/sbin/pfctl/tests/files/pf1075.ok
new file mode 100644
index 000000000000..2369c9410cda
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1075.ok
@@ -0,0 +1 @@
+pass inet from (lo0)/24 to any flags S/SA keep state once
diff --git a/sbin/pfctl/tests/macro.sh b/sbin/pfctl/tests/macro.sh
new file mode 100755
index 000000000000..071c6cb4f426
--- /dev/null
+++ b/sbin/pfctl/tests/macro.sh
@@ -0,0 +1,28 @@
+
+atf_test_case "space" cleanup
+space_head()
+{
+ atf_set descr "Test macros with spaces"
+ atf_set require.kmods "pf"
+}
+
+space_body()
+{
+ echo \"this is\" = \"a variable\" > pf.conf
+ cat pf.conf
+ atf_check -o ignore -e ignore -s exit:1 pfctl -nvf pf.conf
+
+ echo this = \"a variable\" > pf.conf
+ cat pf.conf
+ atf_check -o ignore -s exit:0 pfctl -nvf pf.conf
+}
+
+space_cleanup()
+{
+ rm -f pf.conf
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "space"
+}
diff --git a/sbin/pfctl/tests/pfctl_test.c b/sbin/pfctl/tests/pfctl_test.c
new file mode 100644
index 000000000000..5f0aa7826bb4
--- /dev/null
+++ b/sbin/pfctl/tests/pfctl_test.c
@@ -0,0 +1,341 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org>
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory (Department of Computer Science and
+ * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
+ * DARPA SSITH research programme.
+ *
+ * This work was supported by Innovate UK project 105694, "Digital Security by
+ * Design (DSbD) Technology Platform Prototype".
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <spawn.h>
+#include <sys/module.h>
+#include <sys/sbuf.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+
+/*
+ * Tests 0001-0999 are copied from OpenBSD's regress/sbin/pfctl.
+ * Tests 1001-1999 are ours (FreeBSD's own).
+ *
+ * pf: Run pfctl -nv on pfNNNN.in and check that the output matches pfNNNN.ok.
+ * Copied from OpenBSD. Main differences are some things not working
+ * in FreeBSD:
+ * * The action 'match'
+ * * The command 'set reassemble'
+ * * The 'from'/'to' options together with 'route-to'
+ * * The option 'scrub' (it is an action in FreeBSD)
+ * * Accepting undefined routing tables in actions (??: see pf0093.in)
+ * * The 'route' option
+ * * The 'set queue def' option
+ * selfpf: Feed pfctl output through pfctl again and verify it stays the same.
+ * Copied from OpenBSD.
+ */
+
+extern char **environ;
+
+static struct sbuf *
+read_fd(int fd, size_t sizehint)
+{
+ struct sbuf *sb;
+ ssize_t count;
+ char buffer[MAXBSIZE];
+
+ sb = sbuf_new(NULL, NULL, sizehint, SBUF_AUTOEXTEND);
+ errno = 0;
+ while ((count = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
+ sbuf_bcat(sb, buffer, count);
+ }
+ ATF_REQUIRE_ERRNO(0, count == 0 && "Should have reached EOF");
+ sbuf_finish(sb); /* Ensure NULL-termination */
+ return (sb);
+}
+
+static struct sbuf *
+read_file(const char *filename)
+{
+ struct stat s;
+ struct sbuf *result;
+ int fd;
+
+ errno = 0;
+ ATF_REQUIRE_EQ_MSG(stat(filename, &s), 0, "cannot stat %s", filename);
+ fd = open(filename, O_RDONLY);
+ ATF_REQUIRE_ERRNO(0, fd > 0);
+ result = read_fd(fd, s.st_size);
+ ATF_REQUIRE_ERRNO(0, close(fd) == 0);
+ return (result);
+}
+
+static void
+run_command_pipe(const char *argv[], struct sbuf **output)
+{
+ posix_spawn_file_actions_t action;
+ pid_t pid;
+ int pipefds[2];
+ int status;
+
+ ATF_REQUIRE_ERRNO(0, pipe(pipefds) == 0);
+
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_addclose(&action, STDIN_FILENO);
+ posix_spawn_file_actions_addclose(&action, pipefds[1]);
+ posix_spawn_file_actions_adddup2(&action, pipefds[0], STDOUT_FILENO);
+ posix_spawn_file_actions_adddup2(&action, pipefds[0], STDERR_FILENO);
+
+ printf("Running ");
+ for (int i=0; argv[i] != NULL; i++)
+ printf("%s ", argv[i]);
+ printf("\n");
+
+ status = posix_spawnp(
+ &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
+ ATF_REQUIRE_EQ_MSG(
+ status, 0, "posix_spawn failed: %s", strerror(errno));
+ posix_spawn_file_actions_destroy(&action);
+ close(pipefds[0]);
+
+ (*output) = read_fd(pipefds[1], 0);
+ printf("---\n%s---\n", sbuf_data(*output));
+ ATF_REQUIRE_EQ(waitpid(pid, &status, 0), pid);
+ ATF_REQUIRE_MSG(WIFEXITED(status),
+ "%s returned non-zero! Output:\n %s", argv[0], sbuf_data(*output));
+ close(pipefds[1]);
+}
+
+static void
+run_command(const char *argv[])
+{
+ posix_spawn_file_actions_t action;
+ pid_t pid;
+ int status;
+
+ posix_spawn_file_actions_init(&action);
+ posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
+ posix_spawn_file_actions_addopen(&action, STDERR_FILENO, "/dev/null", O_WRONLY, 0);
+ posix_spawn_file_actions_addopen(&action, STDIN_FILENO, "/dev/zero", O_RDONLY, 0);
+
+ printf("Running ");
+ for (int i=0; argv[i] != NULL; i++)
+ printf("%s ", argv[i]);
+ printf("\n");
+
+ status = posix_spawnp(
+ &pid, argv[0], &action, NULL, __DECONST(char **, argv), environ);
+ posix_spawn_file_actions_destroy(&action);
+ waitpid(pid, &status, 0);
+}
+
+static void
+run_pfctl_test(const char *input_path, const char *output_path,
+ const atf_tc_t *tc, bool test_failure)
+{
+ char input_files_path[PATH_MAX];
+ struct sbuf *expected_output;
+ struct sbuf *real_output;
+
+ /* The test inputs need to be able to use relative includes. */
+ snprintf(input_files_path, sizeof(input_files_path), "%s/files",
+ atf_tc_get_config_var(tc, "srcdir"));
+ ATF_REQUIRE_ERRNO(0, chdir(input_files_path) == 0);
+ expected_output = read_file(output_path);
+
+ const char *argv[] = { "pfctl", "-o", "none", "-nvf", input_path,
+ NULL };
+ run_command_pipe(argv, &real_output);
+
+ if (test_failure) {
+ /*
+ * Error output contains additional strings like line number
+ * or "skipping rule due to errors", so use regexp to see
+ * if the expected error message is there somewhere.
+ */
+ ATF_CHECK_MATCH(sbuf_data(expected_output), sbuf_data(real_output));
+ sbuf_delete(expected_output);
+ } else {
+ ATF_CHECK_STREQ(sbuf_data(expected_output), sbuf_data(real_output));
+ sbuf_delete(expected_output);
+ }
+
+ sbuf_delete(real_output);
+}
+
+static void
+do_pf_test_iface_create(const char *number)
+{
+ struct sbuf *ifconfig_output;
+ char ifname[16] = {0};
+
+ snprintf(ifname, sizeof(ifname), "vlan%s", number);
+ const char *argv[] = { "ifconfig", ifname, "create", NULL};
+ run_command_pipe(argv, &ifconfig_output);
+ sbuf_delete(ifconfig_output);
+
+ const char *argv_inet[] = { "ifconfig", ifname, "inet", "203.0.113.5/30", NULL};
+ run_command_pipe(argv_inet, &ifconfig_output);
+ sbuf_delete(ifconfig_output);
+
+ const char *argv_inet6[] = { "ifconfig", ifname, "inet6", "2001:db8::203.0.113.5/126", NULL};
+ run_command_pipe(argv_inet6, &ifconfig_output);
+ sbuf_delete(ifconfig_output);
+
+ const char *argv_show[] = { "ifconfig", ifname, NULL};
+ run_command_pipe(argv_show, &ifconfig_output);
+ sbuf_delete(ifconfig_output);
+}
+
+static void
+do_pf_test_iface_remove(const char *number)
+{
+ char ifname[16] = {0};
+
+ snprintf(ifname, sizeof(ifname), "vlan%s", number);
+ const char *argv[] = { "ifconfig", ifname, "destroy", NULL};
+ run_command(argv);
+}
+
+static void
+do_pf_test(const char *number, const atf_tc_t *tc)
+{
+ char *input_path;
+ char *expected_path;
+ asprintf(&input_path, "%s/files/pf%s.in",
+ atf_tc_get_config_var(tc, "srcdir"), number);
+ asprintf(&expected_path, "%s/files/pf%s.ok",
+ atf_tc_get_config_var(tc, "srcdir"), number);
+ run_pfctl_test(input_path, expected_path, tc, false);
+ free(input_path);
+ free(expected_path);
+}
+
+static void
+do_pf_test_fail(const char *number, const atf_tc_t *tc)
+{
+ char *input_path;
+ char *expected_path;
+ asprintf(&input_path, "%s/files/pf%s.in",
+ atf_tc_get_config_var(tc, "srcdir"), number);
+ asprintf(&expected_path, "%s/files/pf%s.fail",
+ atf_tc_get_config_var(tc, "srcdir"), number);
+ run_pfctl_test(input_path, expected_path, tc, true);
+ free(input_path);
+ free(expected_path);
+}
+
+static void
+do_selfpf_test(const char *number, const atf_tc_t *tc)
+{
+ char *expected_path;
+ asprintf(&expected_path, "%s/files/pf%s.ok",
+ atf_tc_get_config_var(tc, "srcdir"), number);
+ run_pfctl_test(expected_path, expected_path, tc, false);
+ free(expected_path);
+}
+
+/* Standard tests perform the normal test and then the selfpf test */
+#define PFCTL_TEST(number, descr) \
+ ATF_TC(pf##number); \
+ ATF_TC_HEAD(pf##number, tc) \
+ { \
+ atf_tc_set_md_var(tc, "descr", descr); \
+ atf_tc_set_md_var(tc, "require.kmods", "pf"); \
+ } \
+ ATF_TC_BODY(pf##number, tc) \
+ { \
+ do_pf_test(#number, tc); \
+ } \
+ ATF_TC(selfpf##number); \
+ ATF_TC_HEAD(selfpf##number, tc) \
+ { \
+ atf_tc_set_md_var(tc, "descr", "Self " descr); \
+ atf_tc_set_md_var(tc, "require.kmods", "pf"); \
+ } \
+ ATF_TC_BODY(selfpf##number, tc) \
+ { \
+ do_selfpf_test(#number, tc); \
+ }
+/* Tests for failure perform only the normal test */
+#define PFCTL_TEST_FAIL(number, descr) \
+ ATF_TC(pf##number); \
+ ATF_TC_HEAD(pf##number, tc) \
+ { \
+ atf_tc_set_md_var(tc, "descr", descr); \
+ atf_tc_set_md_var(tc, "require.kmods", "pf"); \
+ } \
+ ATF_TC_BODY(pf##number, tc) \
+ { \
+ do_pf_test_fail(#number, tc); \
+ }
+/* Tests with interface perform only the normal test */
+#define PFCTL_TEST_IFACE(number, descr) \
+ ATF_TC_WITH_CLEANUP(pf##number); \
+ ATF_TC_HEAD(pf##number, tc) \
+ { \
+ atf_tc_set_md_var(tc, "descr", descr); \
+ atf_tc_set_md_var(tc, "execenv", "jail"); \
+ atf_tc_set_md_var(tc, "execenv.jail.params", "vnet"); \
+ atf_tc_set_md_var(tc, "require.kmods", "pf"); \
+ } \
+ ATF_TC_BODY(pf##number, tc) \
+ { \
+ do_pf_test_iface_create(#number); \
+ do_pf_test(#number, tc); \
+ } \
+ ATF_TC_CLEANUP(pf##number, tc) \
+ { \
+ do_pf_test_iface_remove(#number); \
+ }
+#include "pfctl_test_list.inc"
+#undef PFCTL_TEST
+#undef PFCTL_TEST_FAIL
+#undef PFCTL_TEST_IFACE
+
+ATF_TP_ADD_TCS(tp)
+{
+#define PFCTL_TEST(number, descr) \
+ ATF_TP_ADD_TC(tp, pf##number); \
+ ATF_TP_ADD_TC(tp, selfpf##number);
+#define PFCTL_TEST_FAIL(number, descr) \
+ ATF_TP_ADD_TC(tp, pf##number);
+#define PFCTL_TEST_IFACE(number, descr) \
+ ATF_TP_ADD_TC(tp, pf##number);
+#include "pfctl_test_list.inc"
+#undef PFCTL_TEST
+#undef PFCTL_TEST_FAIL
+#undef PFCTL_TEST_IFACE
+
+ return atf_no_error();
+}
diff --git a/sbin/pfctl/tests/pfctl_test_list.inc b/sbin/pfctl/tests/pfctl_test_list.inc
new file mode 100644
index 000000000000..9dd4a590ad8f
--- /dev/null
+++ b/sbin/pfctl/tests/pfctl_test_list.inc
@@ -0,0 +1,186 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2020 Alex Richardson <arichardson@FreeBSD.org>
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory (Department of Computer Science and
+ * Technology) under DARPA contract HR0011-18-C-0016 ("ECATS"), as part of the
+ * DARPA SSITH research programme.
+ *
+ * This work was supported by Innovate UK project 105694, "Digital Security by
+ * Design (DSbD) Technology Platform Prototype".
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * No include guards since this file is included multiple times by pfctl_test
+ * to avoid duplicating code.
+ */
+PFCTL_TEST(0001, "Pass with labels")
+PFCTL_TEST(0002, "Block/pass")
+PFCTL_TEST(0003, "Block/pass with flags")
+PFCTL_TEST(0004, "Block")
+PFCTL_TEST(0005, "Block with variables")
+PFCTL_TEST(0006, "Variables")
+PFCTL_TEST(0007, "Block/pass with return")
+PFCTL_TEST(0008, "Block with address list")
+PFCTL_TEST(0009, "Block with interface list")
+PFCTL_TEST(0010, "Block/pass with return")
+PFCTL_TEST(0011, "Block/pass ICMP")
+PFCTL_TEST(0012, "Pass to subnets")
+PFCTL_TEST(0013, "Pass quick")
+PFCTL_TEST(0014, "Pass quick IPv6")
+PFCTL_TEST(0016, "Pass with no state")
+PFCTL_TEST(0018, "Address lists")
+PFCTL_TEST(0019, "Lists")
+PFCTL_TEST(0020, "Lists")
+PFCTL_TEST(0022, "Set options")
+PFCTL_TEST(0023, "Block on negated interface")
+PFCTL_TEST(0024, "Variable concatenation")
+PFCTL_TEST(0025, "Antispoof")
+PFCTL_TEST(0026, "Block from negated interface")
+PFCTL_TEST(0028, "Block with log and quick")
+PFCTL_TEST(0030, "Line continuation")
+PFCTL_TEST(0031, "Block policy")
+PFCTL_TEST(0032, "Pass to any")
+PFCTL_TEST(0034, "Pass with probability")
+PFCTL_TEST(0035, "Matching on TOS")
+PFCTL_TEST(0038, "Pass with user")
+PFCTL_TEST(0039, "Ordered opts")
+PFCTL_TEST(0040, "Block/pass")
+PFCTL_TEST(0041, "Anchors")
+PFCTL_TEST(0047, "Pass with labels")
+PFCTL_TEST(0048, "Tables")
+PFCTL_TEST(0049, "Broadcast and network modifiers")
+PFCTL_TEST(0050, "Double macro set")
+PFCTL_TEST(0052, "Set optimization")
+PFCTL_TEST(0053, "Pass with labels")
+PFCTL_TEST(0055, "Set options")
+PFCTL_TEST(0056, "State opts")
+PFCTL_TEST(0057, "Variables")
+PFCTL_TEST(0060, "Pass from multicast")
+PFCTL_TEST(0061, "Dynaddr with netmask")
+PFCTL_TEST(0065, "Antispoof with labels")
+PFCTL_TEST(0067, "Tags")
+PFCTL_TEST(0069, "Tags")
+PFCTL_TEST(0070, "Tags")
+PFCTL_TEST(0071, "Tags")
+PFCTL_TEST(0072, "Tags")
+PFCTL_TEST(0074, "Synproxy")
+PFCTL_TEST(0075, "Block quick with tags")
+PFCTL_TEST(0077, "Dynaddr with netmask")
+PFCTL_TEST(0078, "Table with label")
+PFCTL_TEST(0079, "No-route with label")
+PFCTL_TEST(0081, "Address list and table list with no-route")
+PFCTL_TEST(0082, "Pass with interface, table and no-route")
+PFCTL_TEST(0084, "Source track")
+PFCTL_TEST(0085, "Tag macro expansion")
+PFCTL_TEST(0087, "Optimization rule reordering")
+PFCTL_TEST(0088, "Optimization duplicate rules handling")
+PFCTL_TEST(0089, "TCP connection tracking")
+PFCTL_TEST(0090, "Log opts")
+PFCTL_TEST(0091, "Nested anchors")
+PFCTL_TEST(0092, "Comments")
+PFCTL_TEST(0094, "Address ranges")
+PFCTL_TEST(0095, "Include")
+PFCTL_TEST(0096, "Variables")
+PFCTL_TEST(0097, "Divert-to")
+PFCTL_TEST(0098, "Pass")
+PFCTL_TEST(0100, "Anchor with multiple path components")
+PFCTL_TEST(0101, "Prio")
+PFCTL_TEST(0102, "Address lists with mixed address family")
+PFCTL_TEST(0104, "Divert-to with localhost")
+PFCTL_TEST(1001, "Binat")
+PFCTL_TEST(1002, "Set timeout interval")
+PFCTL_TEST(1003, "ALTQ")
+PFCTL_TEST(1004, "ALTQ with Codel")
+PFCTL_TEST(1005, "PR 231323")
+PFCTL_TEST(1006, "pfctl crashes with certain fairq configurations")
+PFCTL_TEST(1007, "Basic ethernet rule")
+PFCTL_TEST(1008, "Ethernet rule with mask length")
+PFCTL_TEST(1009, "Ethernet rule with mask")
+PFCTL_TEST(1010, "POM_STICKYADDRESS test")
+PFCTL_TEST(1011, "Test disabling scrub fragment reassemble")
+PFCTL_TEST(1012, "Test scrub fragment reassemble is default")
+PFCTL_TEST(1013, "Ethernet rule with ridentifier")
+PFCTL_TEST(1014, "Ethernet rule with one label")
+PFCTL_TEST(1015, "Ethernet rule with several labels")
+PFCTL_TEST(1016, "Ethernet rule with ridentifier and one label")
+PFCTL_TEST(1017, "Ethernet rule with ridentifier and several labels")
+PFCTL_TEST(1018, "Test dynamic address mask")
+PFCTL_TEST(1019, "Test pflow option")
+PFCTL_TEST(1020, "Test hashmark and semicolon comment")
+PFCTL_TEST(1021, "Endpoint-independent")
+PFCTL_TEST(1022, "Test received-on")
+PFCTL_TEST(1023, "Test match log(matches)")
+PFCTL_TEST(1024, "nat64")
+PFCTL_TEST(1025, "nat64 with implicit address family")
+PFCTL_TEST(1026, "nat64 with route-to")
+PFCTL_TEST(1027, "nat64 with reply-to")
+PFCTL_TEST(1028, "RDR pool: For RDR rules no port specified means keep port")
+PFCTL_TEST(1029, "RDR pool: A single port is shown")
+PFCTL_TEST(1030, "RDR pool: The default values are shown for RDR rules")
+PFCTL_TEST(1031, "RDR pool: Multiple ports redirected to a single port")
+PFCTL_TEST(1032, "RDR pool: Multiple ports redirected to a port range")
+PFCTL_TEST_FAIL(1033, "RDR pool: static-port can't be used with RDR rules")
+PFCTL_TEST_FAIL(1034, "RDR pool: MAP-E port can't be used with RDR rules")
+PFCTL_TEST(1035, "NAT pool: For NAT rules no port specified means default values")
+PFCTL_TEST(1036, "NAT pool: Default port numbers are not shown, even if explicitly applied")
+PFCTL_TEST(1037, "NAT pool: Single port")
+PFCTL_TEST(1038, "NAT pool: Two ports")
+PFCTL_TEST(1039, "NAT pool: Static port")
+PFCTL_TEST_FAIL(1040, "NAT pool: Static port can't be used with port numbers")
+PFCTL_TEST(1041, "NAT pool: MAP-E is displayed using decimal system")
+PFCTL_TEST_FAIL(1042, "NAT pool: MAP-E port can't be used with static port")
+PFCTL_TEST_FAIL(1043, "NAT pool: MAP-E port can't be used with port numbers")
+PFCTL_TEST(1044, "pool: sticky-address is applied on top of round-robin")
+PFCTL_TEST(1045, "pool: bitmask is allowed for prefixes")
+PFCTL_TEST_FAIL(1046, "pool: bitmask is not allowed for tables")
+PFCTL_TEST_FAIL(1047, "pool: bitmask is not allowed for interfaces in brackets")
+PFCTL_TEST(1048, "pool: random is allowed for prefixes")
+PFCTL_TEST(1049, "pool: round-robin is not set for a single host, even if it looks like a table")
+PFCTL_TEST(1050, "pool: round-robin is set automatically for tables")
+PFCTL_TEST(1051, "pool: round-robin is set automatically for multiple targets")
+PFCTL_TEST(1052, "pool: hosts and table are allowed, round-robin is automatically set")
+PFCTL_TEST(1053, "pool: round-robin is not set automatically for prefixes")
+PFCTL_TEST(1054, "pool: round-robin is allowed for prefixes")
+PFCTL_TEST(1055, "pool: source hash")
+PFCTL_TEST(1056, "af-to: from and to")
+PFCTL_TEST_IFACE(1057, "Interface translation: IPv4 rule, interface without brackets is translated")
+PFCTL_TEST_IFACE(1058, "Interface translation: IPv4 rule, interface without brackets is translated, extra host, round-robin is applied")
+PFCTL_TEST_IFACE(1059, "Interface translation: IPv4 rule, interface with brackets is not translated, round-robin is applied")
+PFCTL_TEST_IFACE(1060, "Interface translation: IPv4 rule, interface with brackets is not translated, extra host, round-robin is applied")
+PFCTL_TEST_IFACE(1061, "Interface translation: IPv6 rule, interface without brackets is translated")
+PFCTL_TEST_IFACE(1062, "Interface translation: IPv6 rule, interface without brackets is translated, extra host, round-robin is applied")
+PFCTL_TEST_IFACE(1063, "Interface translation: IPv6 rule, interface with brackets is not translated, round-robin is applied")
+PFCTL_TEST_IFACE(1064, "Interface translation: IPv6 rule, interface with brackets is not translated, extra host, round robin is applied")
+PFCTL_TEST(1065, "no nat")
+PFCTL_TEST(1066, "no rdr")
+PFCTL_TEST_FAIL(1067, "route-to can't be used on block rules")
+PFCTL_TEST(1068, "max-pkt-rate")
+PFCTL_TEST(1069, "max-pkt-size")
+PFCTL_TEST_FAIL(1070, "include line number")
+PFCTL_TEST(1071, "mask length on (lo0)")
+PFCTL_TEST_FAIL(1072, "Invalid port range")
+PFCTL_TEST(1073, "Filter AF different than route-to AF, with prefer-ipv6-nexthop")
+PFCTL_TEST_FAIL(1074, "Filter AF different than route-to AF, without prefer-ipv6-nexthop")
+PFCTL_TEST(1075, "One shot rule")