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 
115 static WCHAR *FIREWALL_NAME = L"OpenVPN";
116 
117 /*
118  * Default msg handler does nothing
119  */
120 static inline void
121 default_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  */
132 static DWORD
133 add_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 
158 out:
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 
184 DWORD
185 add_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 
363 out:
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 
378 DWORD
379 delete_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  */
403 int
404 get_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 
442 DWORD
443 set_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 */
add_wfp_block_filters
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
FIREWALL_NAME
static WCHAR * FIREWALL_NAME
Definition: wfp_block.c:115
get_interface_metric
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
delete_wfp_block_filters
DWORD delete_wfp_block_filters(HANDLE engine_handle)
Definition: wfp_block.c:379
default_msg_handler
static void default_msg_handler(DWORD err, const char *msg)
Definition: wfp_block.c:121
add_sublayer
static DWORD add_sublayer(GUID uuid)
Definition: wfp_block.c:133
DEFINE_GUID
DEFINE_GUID(FWPM_LAYER_ALE_AUTH_CONNECT_V4, 0xc38d57d1, 0x05a7, 0x4c33, 0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82)
wfp_block_msg_handler_t
void(* wfp_block_msg_handler_t)(DWORD err, const char *msg)
Definition: wfp_block.h:36
UINT64
#define UINT64(c)
Definition: ntlm.c:52
wfp_block.h
syshead.h
set_interface_metric
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
OUT_ON_ERROR
#define OUT_ON_ERROR(err, msg)
Definition: wfp_block.c:126
config.h
session
Definition: keyingmaterialexporter.c:56
FWPM_SESSION_FLAG_DYNAMIC
#define FWPM_SESSION_FLAG_DYNAMIC
Definition: wfp_block.c:48
msg
#define msg(flags,...)
Definition: error.h:144