OpenVPN 3 Core Library
Loading...
Searching...
No Matches
wfp.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
12//
13
14#pragma once
15
16#include <ostream>
17
18#include <openvpn/common/rc.hpp>
25
26#include <fwpmu.h>
27#include <fwpmtypes.h>
28#include <iphlpapi.h>
29#include <wchar.h>
30
31#ifdef __MINGW32__
32
33#include <initguid.h>
34
35// WFP-related defines and GUIDs not in mingw32
36
37#ifndef FWPM_SESSION_FLAG_DYNAMIC
38#define FWPM_SESSION_FLAG_DYNAMIC 0x00000001
39#endif
40
41// defines below are taken from openvpn2 code
42// which likely borrowed them from Windows SDK header fwpmu.h
43
44/* c38d57d1-05a7-4c33-904f-7fbceee60e82 */
45DEFINE_GUID(
46 FWPM_LAYER_ALE_AUTH_CONNECT_V4,
47 0xc38d57d1,
48 0x05a7,
49 0x4c33,
50 0x90,
51 0x4f,
52 0x7f,
53 0xbc,
54 0xee,
55 0xe6,
56 0x0e,
57 0x82);
58
59/* 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 */
60DEFINE_GUID(
61 FWPM_LAYER_ALE_AUTH_CONNECT_V6,
62 0x4a72393b,
63 0x319f,
64 0x44bc,
65 0x84,
66 0xc3,
67 0xba,
68 0x54,
69 0xdc,
70 0xb3,
71 0xb6,
72 0xb4);
73
74/* d78e1e87-8644-4ea5-9437-d809ecefc971 */
75DEFINE_GUID(
76 FWPM_CONDITION_ALE_APP_ID,
77 0xd78e1e87,
78 0x8644,
79 0x4ea5,
80 0x94,
81 0x37,
82 0xd8,
83 0x09,
84 0xec,
85 0xef,
86 0xc9,
87 0x71);
88
89/* c35a604d-d22b-4e1a-91b4-68f674ee674b */
90DEFINE_GUID(
91 FWPM_CONDITION_IP_REMOTE_PORT,
92 0xc35a604d,
93 0xd22b,
94 0x4e1a,
95 0x91,
96 0xb4,
97 0x68,
98 0xf6,
99 0x74,
100 0xee,
101 0x67,
102 0x4b);
103
104/* 4cd62a49-59c3-4969-b7f3-bda5d32890a4 */
105DEFINE_GUID(
106 FWPM_CONDITION_IP_LOCAL_INTERFACE,
107 0x4cd62a49,
108 0x59c3,
109 0x4969,
110 0xb7,
111 0xf3,
112 0xbd,
113 0xa5,
114 0xd3,
115 0x28,
116 0x90,
117 0xa4);
118
119/* 632ce23b-5167-435c-86d7-e903684aa80c */
120DEFINE_GUID(
121 FWPM_CONDITION_FLAGS,
122 0x632ce23b,
123 0x5167,
124 0x435c,
125 0x86,
126 0xd7,
127 0xe9,
128 0x03,
129 0x68,
130 0x4a,
131 0xa8,
132 0x0c);
133
134#endif
135
136namespace openvpn::TunWin {
137
141class WFP : public RC<thread_unsafe_refcount>
142{
143 public:
145
147
151 enum class Block
152 {
153 All,
155 Dns,
156 };
157
158 class ActionBase;
159
163 class Context : public RC<thread_unsafe_refcount>
164 {
165 public:
167
168 private:
169 friend class ActionBase;
170
171 void block(const std::wstring &openvpn_app_path,
172 const NET_IFINDEX itf_index,
173 const Block block_type,
174 std::ostream &log)
175 {
176 unblock(log);
177 wfp.reset(new WFP());
178 wfp->block(openvpn_app_path, itf_index, block_type, log);
179 }
180
181 void unblock(std::ostream &log)
182 {
183 if (wfp)
184 {
185 wfp->reset(log);
186 wfp.reset();
187 }
188 }
189
191 };
192
201 class ActionBase : public Action
202 {
203 public:
209 void execute(std::ostream &log) override
210 {
211 log << to_string() << std::endl;
212 if (block_)
214 else
215 ctx_->unblock(log);
216 }
217
218 std::string to_string() const override
219 {
220 return "ActionBase openvpn_app_path=" + wstring::to_utf8(openvpn_app_path_)
221 + " tap_index=" + std::to_string(itf_index_)
222 + " enable=" + std::to_string(block_);
223 }
224
225 protected:
226 ActionBase(const bool block,
227 const std::wstring &openvpn_app_path,
228 const NET_IFINDEX itf_index,
229 const Block block_type,
230 const Context::Ptr &ctx)
231 : block_(block),
232 openvpn_app_path_(openvpn_app_path),
233 itf_index_(itf_index),
234 block_type_(block_type),
235 ctx_(ctx)
236 {
237 }
238
239 private:
240 const bool block_;
241 const std::wstring openvpn_app_path_;
242 const NET_IFINDEX itf_index_;
245 };
246
247 struct ActionBlock : public ActionBase
248 {
249 ActionBlock(const std::wstring &openvpn_app_path,
250 const NET_IFINDEX itf_index,
251 const Block block_type,
252 const Context::Ptr &wfp)
253 : ActionBase(true, openvpn_app_path, itf_index, block_type, wfp)
254 {
255 }
256 };
257
258 struct ActionUnblock : public ActionBase
259 {
260 ActionUnblock(const std::wstring &openvpn_app_path,
261 const NET_IFINDEX itf_index,
262 const Block block_type,
263 const Context::Ptr &wfp)
264 : ActionBase(false, openvpn_app_path, itf_index, block_type, wfp)
265 {
266 }
267 };
268
269 private:
270 friend class Context;
271
287 void block(const std::wstring &openvpn_app_path,
288 NET_IFINDEX itf_index,
289 Block block_type,
290 std::ostream &log)
291 {
292 // WFP filter/conditions
293 FWPM_FILTER0 filter = {};
294 FWPM_FILTER_CONDITION0 condition[2] = {};
295 FWPM_FILTER_CONDITION0 match_openvpn = {};
296 FWPM_FILTER_CONDITION0 match_port_53 = {};
297 FWPM_FILTER_CONDITION0 match_interface = {};
298 FWPM_FILTER_CONDITION0 match_loopback = {};
299 FWPM_FILTER_CONDITION0 match_not_loopback = {};
300 UINT64 filterid = 0;
301
302 // Get NET_LUID object for adapter
303 NET_LUID itf_luid = adapter_index_to_luid(itf_index);
304
305 // Get app ID
306 unique_ptr_del<FWP_BYTE_BLOB> openvpn_app_id_blob = get_app_id_blob(openvpn_app_path);
307
308 // Populate packet filter layer information
309 {
310 FWPM_SUBLAYER0 subLayer = {};
311 subLayer.subLayerKey = subLayerGUID;
312 subLayer.displayData.name = const_cast<wchar_t *>(L"OpenVPN");
313 subLayer.displayData.description = const_cast<wchar_t *>(L"OpenVPN");
314 subLayer.flags = 0;
315 subLayer.weight = 0x100;
316
317 // Add packet filter to interface
318 const DWORD status = ::FwpmSubLayerAdd0(engineHandle(), &subLayer, NULL);
319 if (status != ERROR_SUCCESS)
320 OPENVPN_THROW(wfp_error, "FwpmSubLayerAdd0 failed with status=0x" << std::hex << status);
321 }
322
323 /* Prepare match conditions */
324 match_openvpn.fieldKey = FWPM_CONDITION_ALE_APP_ID;
325 match_openvpn.matchType = FWP_MATCH_EQUAL;
326 match_openvpn.conditionValue.type = FWP_BYTE_BLOB_TYPE;
327 match_openvpn.conditionValue.byteBlob = openvpn_app_id_blob.get();
328
329 match_port_53.fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
330 match_port_53.matchType = FWP_MATCH_EQUAL;
331 match_port_53.conditionValue.type = FWP_UINT16;
332 match_port_53.conditionValue.uint16 = 53;
333
334 match_interface.fieldKey = FWPM_CONDITION_IP_LOCAL_INTERFACE;
335 match_interface.matchType = FWP_MATCH_EQUAL;
336 match_interface.conditionValue.type = FWP_UINT64;
337 match_interface.conditionValue.uint64 = &itf_luid.Value;
338
339 match_loopback.fieldKey = FWPM_CONDITION_FLAGS;
340 match_loopback.matchType = FWP_MATCH_FLAGS_ALL_SET;
341 match_loopback.conditionValue.type = FWP_UINT32;
342 match_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;
343
344 match_not_loopback.fieldKey = FWPM_CONDITION_FLAGS;
345 match_not_loopback.matchType = FWP_MATCH_FLAGS_NONE_SET;
346 match_not_loopback.conditionValue.type = FWP_UINT32;
347 match_not_loopback.conditionValue.uint32 = FWP_CONDITION_FLAG_IS_LOOPBACK;
348
349 // Prepare filter
350 filter.subLayerKey = subLayerGUID;
351 filter.displayData.name = const_cast<wchar_t *>(L"OpenVPN");
352 filter.weight.type = FWP_UINT8;
353 filter.weight.uint8 = 0xF;
354 filter.filterCondition = condition;
355
356 // Filter #1 -- permit IPv4 requests from OpenVPN app
357 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
358 filter.action.type = FWP_ACTION_PERMIT;
359 filter.numFilterConditions = 1;
360 condition[0] = match_openvpn;
361 add_filter(&filter, NULL, &filterid);
362 log << "permit IPv4 requests from OpenVPN app" << std::endl;
363
364
365 // Filter #2 -- permit IPv6 requests from OpenVPN app
366 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
367 add_filter(&filter, NULL, &filterid);
368 log << "permit IPv6 requests from OpenVPN app" << std::endl;
369
370
371 // Filter #3 -- block IPv4 (DNS) requests, except to loopback, from other apps
372 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
373 filter.action.type = FWP_ACTION_BLOCK;
374 filter.weight.type = FWP_EMPTY;
375 filter.numFilterConditions = 1;
376 condition[0] = match_not_loopback;
377 if (block_type == Block::Dns)
378 {
379 filter.numFilterConditions = 2;
380 condition[1] = match_port_53;
381 }
382 add_filter(&filter, NULL, &filterid);
383 log << "block IPv4 requests from other apps" << std::endl;
384
385
386 // Filter #4 -- block IPv6 (DNS) requests, except to loopback, from other apps
387 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
388 add_filter(&filter, NULL, &filterid);
389 log << "block IPv6 requests from other apps" << std::endl;
390
391
392 // Filter #5 -- allow IPv4 traffic from VPN interface
393 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
394 filter.action.type = FWP_ACTION_PERMIT;
395 filter.numFilterConditions = 1;
396 condition[0] = match_interface;
397 add_filter(&filter, NULL, &filterid);
398 log << "allow IPv4 traffic from TAP" << std::endl;
399
400
401 // Filter #6 -- allow IPv6 traffic from VPN interface
402 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
403 add_filter(&filter, NULL, &filterid);
404 log << "allow IPv6 traffic from TAP" << std::endl;
405
406 if (block_type != Block::AllButLocalDns)
407 {
408 // Filter #7 -- block IPv4 DNS requests to loopback from other apps
409 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
410 filter.action.type = FWP_ACTION_BLOCK;
411 filter.weight.type = FWP_EMPTY;
412 filter.numFilterConditions = 2;
413 condition[0] = match_loopback;
414 condition[1] = match_port_53;
415 add_filter(&filter, NULL, &filterid);
416 log << "block IPv4 DNS requests to loopback from other apps" << std::endl;
417
418
419 // Filter #8 -- block IPv6 DNS requests to loopback from other apps
420 filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
421 add_filter(&filter, NULL, &filterid);
422 log << "block IPv6 DNS requests to loopback from other apps" << std::endl;
423 }
424 }
425
431 void reset(std::ostream &log)
432 {
433 engineHandle.reset(&log);
434 }
435
440 {
441 public:
446 {
447 FWPM_SESSION0 session = {};
448
449 // delete all filters when engine handle is closed
450 session.flags = FWPM_SESSION_FLAG_DYNAMIC;
451
452 const DWORD status = ::FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &handle);
453 if (status != ERROR_SUCCESS)
454 OPENVPN_THROW(wfp_error, "FwpmEngineOpen0 failed with status=0x" << std::hex << status);
455 }
456
464 void reset(std::ostream *log)
465 {
466 if (defined())
467 {
468 const DWORD status = ::FwpmEngineClose0(handle);
469 handle = INVALID_HANDLE_VALUE;
470 if (log)
471 {
472 if (status != ERROR_SUCCESS)
473 *log << "FwpmEngineClose0 failed, status=" << status << std::endl;
474 else
475 *log << "WFP Engine closed" << std::endl;
476 }
477 }
478 }
479
481 {
482 reset(nullptr);
483 }
484
485 bool defined() const
486 {
488 }
489
495 HANDLE operator()() const
496 {
497 return handle;
498 }
499
500 private:
501 EngineHandle(const EngineHandle &) = delete;
503
504 HANDLE handle = INVALID_HANDLE_VALUE;
505 };
506
507 static GUID new_guid()
508 {
509 UUID ret;
510 const RPC_STATUS status = ::UuidCreate(&ret);
511 if (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY)
512 throw wfp_error("UuidCreate failed");
513 return ret;
514 }
515
516 static NET_LUID adapter_index_to_luid(const NET_IFINDEX index)
517 {
518 NET_LUID itf_luid;
519 const NETIO_STATUS ret = ::ConvertInterfaceIndexToLuid(index, &itf_luid);
520 if (ret != NO_ERROR)
521 throw wfp_error("ConvertInterfaceIndexToLuid failed");
522 return itf_luid;
523 }
524
525 static unique_ptr_del<FWP_BYTE_BLOB> get_app_id_blob(const std::wstring &app_path)
526 {
527 FWP_BYTE_BLOB *blob;
528 const DWORD status = ::FwpmGetAppIdFromFileName0(app_path.c_str(), &blob);
529 if (status != ERROR_SUCCESS)
530 OPENVPN_THROW(wfp_error, "FwpmGetAppIdFromFileName0 failed, status=0x" << std::hex << status);
531 return unique_ptr_del<FWP_BYTE_BLOB>(blob, [](FWP_BYTE_BLOB *blob)
532 { ::FwpmFreeMemory0((void **)&blob); });
533 }
534
535 void add_filter(const FWPM_FILTER0 *filter,
536 PSECURITY_DESCRIPTOR sd,
537 UINT64 *id)
538 {
539 const DWORD status = ::FwpmFilterAdd0(engineHandle(), filter, sd, id);
540 if (status != ERROR_SUCCESS)
541 OPENVPN_THROW(wfp_error, "FwpmFilterAdd0 failed, status=0x" << std::hex << status);
542 }
543
544 const GUID subLayerGUID{new_guid()};
546};
547
548} // namespace openvpn::TunWin
The smart pointer class.
Definition rc.hpp:119
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
Reference count base class for objects tracked by RCPtr. Disallows copying and assignment.
Definition rc.hpp:912
Base class for WFP actions.
Definition wfp.hpp:202
const NET_IFINDEX itf_index_
Definition wfp.hpp:242
void execute(std::ostream &log) override
Invoke the context class to block / unblock traffic.
Definition wfp.hpp:209
std::string to_string() const override
Definition wfp.hpp:218
const std::wstring openvpn_app_path_
Definition wfp.hpp:241
ActionBase(const bool block, const std::wstring &openvpn_app_path, const NET_IFINDEX itf_index, const Block block_type, const Context::Ptr &ctx)
Definition wfp.hpp:226
Wrapper class for a WFP session.
Definition wfp.hpp:164
void block(const std::wstring &openvpn_app_path, const NET_IFINDEX itf_index, const Block block_type, std::ostream &log)
Definition wfp.hpp:171
RCPtr< Context > Ptr
Definition wfp.hpp:166
void unblock(std::ostream &log)
Definition wfp.hpp:181
void reset(std::ostream *log)
Close the engine handle.
Definition wfp.hpp:464
EngineHandle()
Open a new WFP session and store the handle.
Definition wfp.hpp:445
HANDLE operator()() const
Return the engine handle.
Definition wfp.hpp:495
EngineHandle(const EngineHandle &)=delete
EngineHandle & operator=(const EngineHandle &)=delete
Add WFP rules to block traffic from escaping the VPN.
Definition wfp.hpp:142
Block
Enum for type of local traffic to block.
Definition wfp.hpp:152
void add_filter(const FWPM_FILTER0 *filter, PSECURITY_DESCRIPTOR sd, UINT64 *id)
Definition wfp.hpp:535
OPENVPN_EXCEPTION(wfp_error)
RCPtr< WFP > Ptr
Definition wfp.hpp:144
void reset(std::ostream &log)
Remove WFP block filters.
Definition wfp.hpp:431
void block(const std::wstring &openvpn_app_path, NET_IFINDEX itf_index, Block block_type, std::ostream &log)
Add WFP block filters to prevent VPN traffic from leaking.
Definition wfp.hpp:287
static GUID new_guid()
Definition wfp.hpp:507
static unique_ptr_del< FWP_BYTE_BLOB > get_app_id_blob(const std::wstring &app_path)
Definition wfp.hpp:525
static NET_LUID adapter_index_to_luid(const NET_IFINDEX index)
Definition wfp.hpp:516
EngineHandle engineHandle
Definition wfp.hpp:545
const GUID subLayerGUID
Definition wfp.hpp:544
#define OPENVPN_THROW(exc, stuff)
DNS utilities for Windows.
bool defined(HANDLE handle)
Definition handle.hpp:25
std::unique_ptr< T, std::function< void(T *)> > unique_ptr_del
Definition uniqueptr.hpp:21
ActionBlock(const std::wstring &openvpn_app_path, const NET_IFINDEX itf_index, const Block block_type, const Context::Ptr &wfp)
Definition wfp.hpp:249
ActionUnblock(const std::wstring &openvpn_app_path, const NET_IFINDEX itf_index, const Block block_type, const Context::Ptr &wfp)
Definition wfp.hpp:260
std::string ret