Dependencies

Description

Firewall App Backend consists of libraries (that represent the NetworkFilterManager.xcodeproj project goals): NetworkFilterManagerNetworkFilter and the Extension target.

The kernel is located in a C++ library – NetworkFilterManager (with files: nf.cpp/nf.hpp) and provides a C interface (nf.h). It provides interface data structures and various functions for working with rules and statistics. Classes that store rules are implemented in the C++ library – nf::RuleStorage; the class nf::NetworkFilter implements the logic of the filter that works in Extension. The functions declared in C API are located in a swift library – NetworkFilter. Such functions are used to store statistics and rules on the front-end.

The NetworkFilter library provides a swift network filter interface and is used in a UI data model – NetFilterModel. It contains data structures (similar to nf.h) and the main interface that enables the UI to interact with the filter – the NetworkFilterManager protocol:

public protocol NetworkFilterManager: AnyObject
Unknown macro: {   typealias UpdateCallback = (_ completion}
var statisticEnabled: Bool
 
func statistics(application: String) -> [Statistic]?
 
func rules(options: RulesOptions) -> [Rule]
 
func updateRule(_ rule: Rule) throws
 
func removeRule(id: UInt64) throws
 
func registerOnlineAccessChecker(_ callback: @escaping OnlineAccessCheckCallback)
 
func registerRulesUpdateCallback(_ callback: @escaping UpdateCallback)
 
func registerStatisticUpdateCallback(_ callback: @escaping UpdateCallback)
 
} 

Extension is aimed to build network extensions. It configures mach servers to process requests from the application GUI. It also connects nf::NetworkFilter and NetworkExtension.framework call handlers that enable performing access audits:

([NEFilterNewFlowVerdict|apple-reference-documentation://hcT2jHsOuj] *)handleNewFlow:([NEFilterFlow|apple-reference-documentation://hc8AcbiZUm] *)flow;

([NEFilterDataVerdict|apple-reference-documentation://hc3IabEzma] *)handleInboundDataFromFlow:([NEFilterFlow|apple-reference-documentation://hc8AcbiZUm] *)flow readBytesStartOffset:([NSUInteger|apple-reference-documentation://hcM8QYD0EG])offset readBytes:([NSData|apple-reference-documentation://hcszIi2z4T] *)readBytes;

([NEFilterDataVerdict|apple-reference-documentation://hc3IabEzma] *)handleInboundDataCompleteForFlow:([NEFilterFlow|apple-reference-documentation://hc8AcbiZUm] *)flow;

([NEFilterDataVerdict|apple-reference-documentation://hc3IabEzma] *)handleOutboundDataFromFlow:([NEFilterFlow|apple-reference-documentation://hc8AcbiZUm] *)flow readBytesStartOffset:([NSUInteger|apple-reference-documentation://hcM8QYD0EG])offset readBytes:([NSData|apple-reference-documentation://hcszIi2z4T] *)readBytes;

([NEFilterDataVerdict|apple-reference-documentation://hc3IabEzma] *)handleOutboundDataCompleteForFlow:([NEFilterFlow|apple-reference-documentation://hc8AcbiZUm] *)flow; 

This project also contains FilterDelegate that is used to send requests/notifications to the UI of the extension client.

IPC

The Interprocess Communication (IPC) uses mach to simplify interactions that form libraries: mach-cpp (C+17 implementation) and Mach (swift implementation). The mach-cpp library collaborates with the mcom library that represents the C++ wrapper for the CoreFoundation framework, dispatch queues, Disk Arbitration framework, Security framework, IOKit framework and provides several useful abstractions, such as:

  • Sync – synchronizes access to a specified value.
  • FileLock  – performs the file synchronization.
  • Encoder & Decoder – serializes and deserializes user types.

Encoder and Decoder are used to send primitive and complex types via mach without generating codes. However, they require to implement the MachEncodable / MachDecodable protocols (in swift) and to define template<class T> mach::Codable (in C++).

The libraries contain encoding of primitive types, std::optional, std::vector, std::variant, std::string, std::string_view.

The following provides an interaction scheme of the aforementioned libraries:

Extension checks in launchd and receives its right port that creates mach::Server:

auto receive_right = mach::BootstrapCheckIn(service_name);
mach::Server server{*receive_right};  

Call handlers should be registered on the created server:

server.AddHandler(201, [&](nf::FilterMode mode) { filter.SetMode(mode); }); 

After that, client interactions with this server will be performed as follows:

public var mode: FilterResult {
 
get 
Unknown macro: { ... }
 
set 
Unknown macro: {       *try*? Message.send(id}
 
} 

Where port represents a send port that should be obtained as follows:

port = try MachSendPort.lookup(name: serviceName)

Workarounds

When the filter switched on, Apple services (such as iTunes and AppStore) did not work, even if the full access policy selected. Therefore, as a workaround, it’s necessary to create access rules for Apple hosts (see-(void)startFilterWithCompletionHandler:(void (^)([NSError|apple-reference-documentation://hcInK_-ThL] *error))completionHandler; in the FilterDataProvider implementation).

Another problem is that some applications use services to connect with hosts. It is not always clear which application initiates this action. For instance, Safari uses WebKit that is located at:

/System/Library/Frameworks/WebKit.framework/Versions/A/XPCServices/com.apple.WebKit.Networking.xpc/Contents/MacOS/com.apple.WebKit.Networking.

In case only Safari is blocked, access will not be limited. Since Firewall displays application connections, it is necessary to develop associations of an application and its used and services. 

For Safari and Webkit, this should be done in the – (std::optional<nf::Application>)applicationForFlow:(NEFilterFlow *)flow; FilterDataProvider implementation .