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