OpenVPN
wfp_block.c
Go to the documentation of this file.
1/*
2 * OpenVPN -- An application to securely tunnel IP networks
3 * over a single TCP/UDP port, with support for SSL/TLS-based
4 * session authentication and key exchange,
5 * packet encryption, packet authentication, and
6 * packet compression.
7 *
8 * Copyright (C) 2002-2026 OpenVPN Inc <sales@openvpn.net>
9 * 2015-2016 <iam@valdikss.org.ru>
10 * 2016 Selva Nair <selva.nair@gmail.com>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License version 2
14 * as published by the Free Software Foundation.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, see <https://www.gnu.org/licenses/>.
23 */
24
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
28
29#include "syshead.h"
30
31#ifdef _WIN32
32
33#include <fwpmu.h>
34#include <initguid.h>
35#include <fwpmtypes.h>
36#include <winsock2.h>
37#include <ws2ipdef.h>
38#include <iphlpapi.h>
39
40#include "wfp_block.h"
41
42/*
43 * WFP-related defines and GUIDs not in mingw32
44 */
45
46#ifndef FWPM_SESSION_FLAG_DYNAMIC
47#define FWPM_SESSION_FLAG_DYNAMIC 0x00000001
48#endif
49
50/* c38d57d1-05a7-4c33-904f-7fbceee60e82 */
51DEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V4, 0xc38d57d1, 0x05a7, 0x4c33, 0x90, 0x4f, 0x7f, 0xbc,
52 0xee, 0xe6, 0x0e, 0x82);
53
54/* 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 */
55DEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V6, 0x4a72393b, 0x319f, 0x44bc, 0x84, 0xc3, 0xba, 0x54,
56 0xdc, 0xb3, 0xb6, 0xb4);
57
58/* d78e1e87-8644-4ea5-9437-d809ecefc971 */
59DEFINE_GUID(FWPM_CONDITION_ALE_APP_ID, 0xd78e1e87, 0x8644, 0x4ea5, 0x94, 0x37, 0xd8, 0x09, 0xec,
60 0xef, 0xc9, 0x71);
61
62/* c35a604d-d22b-4e1a-91b4-68f674ee674b */
63DEFINE_GUID(FWPM_CONDITION_IP_REMOTE_PORT, 0xc35a604d, 0xd22b, 0x4e1a, 0x91, 0xb4, 0x68, 0xf6, 0x74,
64 0xee, 0x67, 0x4b);
65
66/* 4cd62a49-59c3-4969-b7f3-bda5d32890a4 */
67DEFINE_GUID(FWPM_CONDITION_IP_LOCAL_INTERFACE, 0x4cd62a49, 0x59c3, 0x4969, 0xb7, 0xf3, 0xbd, 0xa5,
68 0xd3, 0x28, 0x90, 0xa4);
69
70/* 632ce23b-5167-435c-86d7-e903684aa80c */
71DEFINE_GUID(FWPM_CONDITION_FLAGS, 0x632ce23b, 0x5167, 0x435c, 0x86, 0xd7, 0xe9, 0x03, 0x68, 0x4a,
72 0xa8, 0x0c);
73
74/* UUID of WFP sublayer used by all instances of openvpn
75 * 2f660d7e-6a37-11e6-a181-001e8c6e04a2 */
76DEFINE_GUID(OPENVPN_WFP_BLOCK_SUBLAYER, 0x2f660d7e, 0x6a37, 0x11e6, 0xa1, 0x81, 0x00, 0x1e, 0x8c,
77 0x6e, 0x04, 0xa2);
78
79static WCHAR *FIREWALL_NAME = L"OpenVPN";
80
81/*
82 * Default msg handler does nothing
83 */
84static inline void
85default_msg_handler(DWORD err, const char *msg)
86{
87 return;
88}
89
90#define OUT_ON_ERROR(err, msg) \
91 if (err) \
92 { \
93 msg_handler(err, msg); \
94 goto out; \
95 }
96
97/*
98 * Add a persistent sublayer with specified uuid.
99 */
100static DWORD
101add_sublayer(GUID uuid)
102{
103 FWPM_SESSION0 session;
104 HANDLE engine = NULL;
105 DWORD err = 0;
106 FWPM_SUBLAYER0 sublayer;
107
108 memset(&session, 0, sizeof(session));
109 memset(&sublayer, 0, sizeof(sublayer));
110
111 err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engine);
112 if (err != ERROR_SUCCESS)
113 {
114 goto out;
115 }
116
117 sublayer.subLayerKey = uuid;
118 sublayer.displayData.name = FIREWALL_NAME;
119 sublayer.displayData.description = FIREWALL_NAME;
120 sublayer.flags = 0;
121 sublayer.weight = 0x100;
122
123 /* Add sublayer to the session */
124 err = FwpmSubLayerAdd0(engine, &sublayer, NULL);
125
126out:
127 if (engine)
128 {
129 FwpmEngineClose0(engine);
130 }
131 return err;
132}
133
134#if defined(__GNUC__) || defined(__clang__)
135#pragma GCC diagnostic push
136#pragma GCC diagnostic ignored "-Wsign-compare"
137#endif
138
139/*
140 * Block outgoing local traffic, possibly DNS only, except for
141 * (i) adapter with the specified index (and loopback, if all is blocked)
142 * OR
143 * (ii) processes with the specified executable path
144 * The firewall filters added here are automatically removed when the process exits or
145 * on calling delete_wfp_block_filters().
146 * Arguments:
147 * engine_handle : On successful return contains the handle for a newly opened fwp session
148 * in which the filters are added.
149 * May be closed by passing to delete_wfp_block_filters to remove the filters.
150 * index : The index of adapter for which traffic is permitted.
151 * exe_path : Path of executable for which traffic is permitted.
152 * msg_handler : An optional callback function for error reporting.
153 * dns_only : Whether the blocking filters should apply for DNS only.
154 * Returns 0 on success, a non-zero status code of the last failed action on failure.
155 */
156
157DWORD
158add_wfp_block_filters(HANDLE *engine_handle, int index, const WCHAR *exe_path,
159 wfp_block_msg_handler_t msg_handler, BOOL dns_only)
160{
161 FWPM_SESSION0 session = { 0 };
162 FWPM_SUBLAYER0 *sublayer_ptr = NULL;
163 NET_LUID itf_luid;
164 UINT64 filterid;
165 FWP_BYTE_BLOB *openvpnblob = NULL;
166 FWPM_FILTER0 Filter = { 0 };
167 FWPM_FILTER_CONDITION0 Condition[2];
168 FWPM_FILTER_CONDITION0 match_openvpn = { 0 };
169 FWPM_FILTER_CONDITION0 match_port_53 = { 0 };
170 FWPM_FILTER_CONDITION0 match_interface = { 0 };
171 FWPM_FILTER_CONDITION0 match_loopback = { 0 };
172 FWPM_FILTER_CONDITION0 match_not_loopback = { 0 };
173 DWORD err = 0;
174
175 if (!msg_handler)
176 {
177 msg_handler = default_msg_handler;
178 }
179
180 /* Add temporary filters which don't survive reboots or crashes. */
182
183 *engine_handle = NULL;
184
185 err = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, engine_handle);
186 OUT_ON_ERROR(err, "FwpEngineOpen: open fwp session failed");
187 msg_handler(0, "WFP Block: WFP engine opened");
188
189 /* Check sublayer exists and add one if it does not. */
190 if (FwpmSubLayerGetByKey0(*engine_handle, &OPENVPN_WFP_BLOCK_SUBLAYER, &sublayer_ptr)
191 == ERROR_SUCCESS)
192 {
193 msg_handler(0, "WFP Block: Using existing sublayer");
194 FwpmFreeMemory0((void **)&sublayer_ptr);
195 }
196 else
197 { /* Add a new sublayer -- as another process may add it in the meantime,
198 * do not treat "already exists" as an error */
199 err = add_sublayer(OPENVPN_WFP_BLOCK_SUBLAYER);
200
201 if (err == FWP_E_ALREADY_EXISTS || err == ERROR_SUCCESS)
202 {
203 msg_handler(0, "WFP Block: Added a persistent sublayer with pre-defined UUID");
204 }
205 else
206 {
207 OUT_ON_ERROR(err, "add_sublayer: failed to add persistent sublayer");
208 }
209 }
210
211 err = ConvertInterfaceIndexToLuid(index, &itf_luid);
212 OUT_ON_ERROR(err, "Convert interface index to luid failed");
213
214 err = FwpmGetAppIdFromFileName0(exe_path, &openvpnblob);
215 OUT_ON_ERROR(err, "Get byte blob for openvpn executable name failed");
216
217 /* Prepare match conditions */
218 match_openvpn.fieldKey = FWPM_CONDITION_ALE_APP_ID;
219 match_openvpn.matchType = FWP_MATCH_EQUAL;
220 match_openvpn.conditionValue.type = FWP_BYTE_BLOB_TYPE;
221 match_openvpn.conditionValue.byteBlob = openvpnblob;
222
223 match_port_53.fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
224 match_port_53.matchType = FWP_MATCH_EQUAL;
225 match_port_53.conditionValue.type = FWP_UINT16;
226 match_port_53.conditionValue.uint16 = 53;
227
228 match_interface.fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE;
229 match_interface.matchType = FWP_MATCH_EQUAL;
230 match_interface.conditionValue.type = FWP_UINT64;
231 match_interface.conditionValue.uint64 = &itf_luid.Value;
232
233 match_loopback.fieldKey = FWPM_CONDITION_FLAGS;
234 match_loopback.matchType = FWP_MATCH_FLAGS_ALL_SET;
235 match_loopback.conditionValue.type = FWP_UINT32;
236 match_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;
237
238 match_not_loopback.fieldKey = FWPM_CONDITION_FLAGS;
239 match_not_loopback.matchType = FWP_MATCH_FLAGS_NONE_SET;
240 match_not_loopback.conditionValue.type = FWP_UINT32;
241 match_not_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;
242
243 /* Prepare filter. */
244 Filter.subLayerKey = OPENVPN_WFP_BLOCK_SUBLAYER;
245 Filter.displayData.name = FIREWALL_NAME;
246 Filter.weight.type = FWP_UINT8;
247 Filter.weight.uint8 = 0xF;
248 Filter.filterCondition = Condition;
249 Filter.numFilterConditions = 1;
250
251 /* First filter. Permit IPv4 from OpenVPN itself. */
252 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
253 Filter.action.type = FWP_ACTION_PERMIT;
254 Condition[0] = match_openvpn;
255 if (dns_only)
256 {
257 Filter.numFilterConditions = 2;
258 Condition[1] = match_port_53;
259 }
260 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
261 OUT_ON_ERROR(err, "Add filter to permit IPv4 traffic from OpenVPN failed");
262
263 /* Second filter. Permit IPv6 from OpenVPN itself. */
264 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
265 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
266 OUT_ON_ERROR(err, "Add filter to permit IPv6 traffic from OpenVPN failed");
267
268 msg_handler(0, "WFP Block: Added permit filters for exe_path");
269
270 /* Third filter. Block IPv4 to port 53 or all except loopback. */
271 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
272 Filter.action.type = FWP_ACTION_BLOCK;
273 Filter.weight.type = FWP_EMPTY;
274 Filter.numFilterConditions = 1;
275 Condition[0] = match_not_loopback;
276 if (dns_only)
277 {
278 Filter.numFilterConditions = 2;
279 Condition[1] = match_port_53;
280 }
281 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
282 OUT_ON_ERROR(err, "Add filter to block IPv4 traffic failed");
283
284 /* Fourth filter. Block IPv6 to port 53 or all besides loopback */
285 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
286 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
287 OUT_ON_ERROR(err, "Add filter to block IPv6 traffic failed");
288
289 msg_handler(0, "WFP Block: Added block filters for all interfaces");
290
291 /* Fifth filter. Permit all IPv4 or just DNS traffic for the VPN interface.
292 * Use a non-zero weight so that the permit filters get higher priority
293 * over the block filter added with automatic weighting */
294 Filter.weight.type = FWP_UINT8;
295 Filter.weight.uint8 = 0xE;
296 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
297 Filter.action.type = FWP_ACTION_PERMIT;
298 Filter.numFilterConditions = 1;
299 Condition[0] = match_interface;
300 if (dns_only)
301 {
302 Filter.numFilterConditions = 2;
303 Condition[1] = match_port_53;
304 }
305 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
306 OUT_ON_ERROR(err, "Add filter to permit IPv4 traffic through VPN interface failed");
307
308 /* Sixth filter. Permit all IPv6 or just DNS traffic for the VPN interface.
309 * Use same weight as IPv4 filter */
310 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
311 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
312 OUT_ON_ERROR(err, "Add filter to permit IPv6 traffic through VPN interface failed");
313
314 msg_handler(0, "WFP Block: Added permit filters for VPN interface");
315
316 /* Seventh Filter. Block IPv4 DNS requests to loopback from other apps */
317 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
318 Filter.action.type = FWP_ACTION_BLOCK;
319 Filter.weight.type = FWP_EMPTY;
320 Filter.numFilterConditions = 2;
321 Condition[0] = match_loopback;
322 Condition[1] = match_port_53;
323 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
324 OUT_ON_ERROR(err, "Add filter to block IPv4 DNS traffic to loopback failed");
325
326 /* Eighth Filter. Block IPv6 DNS requests to loopback from other apps */
327 Filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
328 err = FwpmFilterAdd0(*engine_handle, &Filter, NULL, &filterid);
329 OUT_ON_ERROR(err, "Add filter to block IPv6 DNS traffic to loopback failed");
330
331 msg_handler(0, "WFP Block: Added block filters for DNS traffic to loopback");
332
333out:
334 if (openvpnblob)
335 {
336 FwpmFreeMemory0((void **)&openvpnblob);
337 }
338
339 if (err && *engine_handle)
340 {
341 FwpmEngineClose0(*engine_handle);
342 *engine_handle = NULL;
343 }
344
345 return err;
346}
347
348#if defined(__GNUC__) || defined(__clang__)
349#pragma GCC diagnostic pop
350#endif
351
352DWORD
353delete_wfp_block_filters(HANDLE engine_handle)
354{
355 DWORD err = 0;
356 /*
357 * For dynamic sessions closing the engine removes all filters added in the session
358 */
359 if (engine_handle)
360 {
361 err = FwpmEngineClose0(engine_handle);
362 }
363 return err;
364}
365
366/*
367 * Return interface metric value for the specified interface index.
368 *
369 * Arguments:
370 * index : The index of TAP adapter.
371 * family : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).
372 * is_auto : On return set to true if automatic metric is in use.
373 * Unused if NULL.
374 *
375 * Returns positive metric value or -1 on error.
376 */
377int
378get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto)
379{
380 DWORD err = 0;
381 MIB_IPINTERFACE_ROW ipiface;
382 InitializeIpInterfaceEntry(&ipiface);
383 ipiface.Family = family;
384 ipiface.InterfaceIndex = index;
385
386 if (is_auto)
387 {
388 *is_auto = 0;
389 }
390 err = GetIpInterfaceEntry(&ipiface);
391
392 /* On Windows metric is never > INT_MAX so return value of int is ok.
393 * But we check for overflow nevertheless.
394 */
395 if (err == NO_ERROR && ipiface.Metric <= INT_MAX)
396 {
397 if (is_auto)
398 {
399 *is_auto = ipiface.UseAutomaticMetric;
400 }
401 return (int)ipiface.Metric;
402 }
403 return -1;
404}
405
406/*
407 * Sets interface metric value for specified interface index.
408 *
409 * Arguments:
410 * index : The index of TAP adapter.
411 * family : Address family (AF_INET for IPv4 and AF_INET6 for IPv6).
412 * metric : Metric value. 0 for automatic metric.
413 * Returns 0 on success, a non-zero status code of the last failed action on failure.
414 */
415
416DWORD
417set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, const ULONG metric)
418{
419 DWORD err = 0;
420 MIB_IPINTERFACE_ROW ipiface;
421 InitializeIpInterfaceEntry(&ipiface);
422 ipiface.Family = family;
423 ipiface.InterfaceIndex = index;
424 err = GetIpInterfaceEntry(&ipiface);
425 if (err == NO_ERROR)
426 {
427 if (family == AF_INET)
428 {
429 /* required for IPv4 as per MSDN */
430 ipiface.SitePrefixLength = 0;
431 }
432 ipiface.Metric = metric;
433 if (metric == 0)
434 {
435 ipiface.UseAutomaticMetric = TRUE;
436 }
437 else
438 {
439 ipiface.UseAutomaticMetric = FALSE;
440 }
441 err = SetIpInterfaceEntry(&ipiface);
442 if (err == NO_ERROR)
443 {
444 return 0;
445 }
446 }
447 return err;
448}
449
450#endif /* ifdef _WIN32 */
#define msg(flags,...)
Definition error.h:152
int get_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, int *is_auto)
Return interface metric value for the specified interface index.
Definition wfp_block.c:378
DEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V4, 0xc38d57d1, 0x05a7, 0x4c33, 0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82)
#define OUT_ON_ERROR(err, msg)
Definition wfp_block.c:90
static void default_msg_handler(DWORD err, const char *msg)
Definition wfp_block.c:85
static DWORD add_sublayer(GUID uuid)
Definition wfp_block.c:101
DWORD set_interface_metric(const NET_IFINDEX index, const ADDRESS_FAMILY family, const ULONG metric)
Sets interface metric value for specified interface index.
Definition wfp_block.c:417
static WCHAR * FIREWALL_NAME
Definition wfp_block.c:79
DWORD delete_wfp_block_filters(HANDLE engine_handle)
Definition wfp_block.c:353
#define FWPM_SESSION_FLAG_DYNAMIC
Definition wfp_block.c:47
DWORD add_wfp_block_filters(HANDLE *engine_handle, int index, const WCHAR *exe_path, wfp_block_msg_handler_t msg_handler, BOOL dns_only)
Definition wfp_block.c:158
void(* wfp_block_msg_handler_t)(DWORD err, const char *msg)
Definition wfp_block.h:35