\section{Firewall} \subsection{Software Firewall} A software firewall is inspecting data that goes in and out of the device. It has to be installed on each device in the network. Therefore, can only protect one device at a time. Looking at already existing solutions for linux and other operating systems, rules and settings can be identified that need to be implemented for this firewall. \subsection{UFW (Uncomplicated Firewall)} To see how a firewall works, UFW was analyzed. A look at the table provides following information: \begin{figure}[H] \begin{center} \includegraphics[width=0.6\textwidth]{ufw} \caption{UFW} \label{fig:UFW} \end{center} \end{figure} A destination port on the device, the action of the firewall and the IP-Address from where the request originated can be setup. Also the protocol that the rule applies to, can be chosen with TCP or UDP. \newpage \subsection{Parameter} After analyzing existing solutions following firewall parameters were implemented: \lstset{style=c++} \begin{lstlisting} typedef enum firewall_targets : uint8_t { TARGET_DROP = 1, TARGET_ACCEPT = 2, } firewall_target_t; typedef enum firewall_protocols : uint8_t { PROTOCOL_TCP = 6, PROTOCOL_UDP = 17, PROTOCOL_ALL = 255, } firewall_protocol_t; static const uint8_t IPV4ADDRESS_LENGTH = 16; typedef struct firewall_rules { uint8_t key; char ip[IPV4ADDRESS_LENGTH]; uint16_t port_from; uint16_t port_to; firewall_protocol_t protocol; firewall_target_t target; struct firewall_rules *next; } firewall_rule_t; \end{lstlisting} A port can be a maximum of 65535, and was chosen because LwIP (Section \ref{lwip}) uses it for the ports in the respective header. Target as well as protocol are enums for the available options. LwIP uses the same specified values for their protocols (Section \ref{lwip_analysis}). To block a range of ports, there is a \verb|port_from| and \verb|port_to|. The firewall will store all the rules as linked list to dynamically add and remove rules. \subsection{IwIP} \label{lwip} lwIP is a small independent implementation of the TCP/IP protocol suite and is used in the esp-core for network communication. This is the place the firewall need to check for the incoming traffic in order to drop, reject or pass packets based on the rules. \cite[cf.][]{lwip} \newpage \subsubsection{Analysing} \label{lwip_analysis} First step is to analyze the code to find out where the packets are getting handled. Looking in \verb|~/.platformio/packages/framework-espidf/components/lwip/lwip/src/core/ipv4| there is a function: \verb|err_t ip4_input(struct pbuf *p, struct netif *inp)| to consume all incoming packages. Simply placing a logger can quickly show that this is the place to put the firewall filter. \begin{verbatim} if (ip4_addr4_16_val(iphdr->src) == 211) { ESP_LOGI("HOOK", "%3" U16_F, "% " U16_F "% " U16_F "% " U16_F "% " U16_F, (u16_t)IPH_PROTO(iphdr) ip4_addr1_16_val(iphdr->src), ip4_addr2_16_val(iphdr->src), ip4_addr3_16_val(iphdr->src), ip4_addr4_16_val(iphdr->src)); } \end{verbatim} Following output can be seen when sending a ping from the machine with IP-Address \verb|10.93.0.211| to the esp. ICMP is therefore marked with protocol 1. \begin{verbatim} I (x) HOOK: 1 10 93 0 211 \end{verbatim} Sending a UDP or TCP package to the ESP (IP-Address: \verb|10.93.0.246|) can be done by executing the python code in the repository, or simply executing an nmap\footnote{\href{https://nmap.org/}{https://nmap.org/}} scan with UDP and TCP. \begin{verbatim} python3 tester.py -i 10.93.0.246 -p 80 -t TCP python3 tester.py -i 10.93.0.246 -p 22 -t UPD \end{verbatim} Following output can be registered. \begin{verbatim} I (x) HOOK: 6 10 93 0 211 I (x) HOOK: 17 10 93 0 211 \end{verbatim} Looking at the printed protocols this means evidentially how protocols are identified in LwIP: $$ "ICMP" \equiv 1 $$ $$ "TCP" \equiv 6 $$ $$ "UDP" \equiv 17 $$ \subsubsection{Arduino as an ESP-IDF component} LwIP needs to be recompiled to register hooks (Section \ref{sec:hooks}). Therefore the following steps are performed to compile our code with Arduino as an ESP-IDF component on an ESP32. After initializing an empty ESP-IDF project with PlatformIO the \verb|platformio.ini| file would look like this: \begin{verbatim} [platformio] default_envs = esp32 [env:esp32] platform = espressif32 board = az-delivery-devkit-v4 framework = espidf monitor_speed = 115200 \end{verbatim} Following commands need to be executed inside a fresh ESP-IDF project folder, in order to add Arduino into the setup: \lstset{style=shell} \begin{lstlisting} mkdir -p components && \ cd components && \ git clone https://github.com/espressif/arduino-esp32.git arduino && \ cd arduino && \ git submodule update --init --recursive && \ cd ../.. \end{lstlisting} After successful cloning of the git repository, some changes need to be written into the ESP-IDF config file with the help of \verb|menuconfig|: \begin{verbatim} platformio run --target menuconfig --environment esp32 \end{verbatim} Navigating to the section Arduino Configuration, usage of setup() and loop() functions can be turned on with the option ``Autostart Arduino setup and loop on boot''. After these steps Arduino code can be written and executed like expected, but with the advantages of compiling the whole esp32-core with the specified settings and hooks. \cite[][]{arduino-esp-idf} \newpage \subsubsection{Using a Hook} \label{sec:hooks} After learning the protocols, a hook needs to be registered in order to filter the packets based on our rules. To register the hook that suits our project, \verb|LWIP_HOOK_IP4_INPUT| needs to be set as \verb|build_flag| in the \verb|platformio.ini| file to overwrite it in LwIP. This is an easy way of testing if it works as expected, but should be written in a function for any other use-case. \begin{verbatim} build_flags = '-DLWIP_HOOK_IP4_INPUT(pbuf, input_netif)=({ESP_LOGI("HOOK","TEST");0;})' \end{verbatim} \cite[cf.][]{lwip} After the successful test of the logging hook, an implementation of a function is needed in order to integrate it with the firewall API written to change and create rules. In order to register a function LwIP needs to know where the prototypes are declared. Therefore, in the \verb|platformio.ini| file, the \verb|build_flags| need to be adjusted. \begin{verbatim} build_flags = '-Iinclude' '-DESP_IDF_LWIP_HOOK_FILENAME="lwip_hooks.h"' \end{verbatim} The file \verb|lwip_hooks.h| has the necessary information for LwIP to know how the function will be called. \lstset{style=c++} \begin{lstlisting} int lwip_hook_ip4_input(struct pbuf *pbuf, struct netif *input_netif); #define LWIP_HOOK_IP4_INPUT lwip_hook_ip4_input \end{lstlisting} After specifying the prototype the function can be placed in the main.cpp file to be compiled and run. \newpage \subsection{Benchmark} To test the performance of the firewall with rules and without rules, as well as many rules, the time is stopped at the beginning and the end of the hook. \subsubsection{Code} \lstset{style=c++} \begin{lstlisting} void print_time_taken(struct timeval start, fw::firewall_target_t target) { struct timeval stop; gettimeofday(&stop, NULL); u32_t time_taken = (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec; Serial.println(time_taken); } int lwip_hook_ip4_input(struct pbuf *pbuf, struct netif *input_netif) { // Firewall is not setup yet if (firewall != NULL) { struct timeval start; gettimeofday(&start, NULL); if (firewall->is_packet_allowed(pbuf)) { print_time_taken(start, fw::TARGET_ACCEPT); return 0; } else { print_time_taken(start, fw::TARGET_DROP); pbuf_free(pbuf); return 1; } } return 0; } \end{lstlisting} \newpage \subsubsection{Result} The results in milliseconds are copied into a spreadsheet to create an chart with the measured processing time of a packet in the hook. The tests were done with 1,5,10 and 15 rules. As the linear trendlines indicate, the amount of rules are heavily responsible for a longer processing time. \begin{figure}[H] \begin{center} \includegraphics[width=\textwidth]{chart} \caption{Benchmark graph} \label{fig:Benchmark graph} \end{center} \end{figure} Without any rule, the processing of the included hook takes between 23 to 24 milliseconds. No comparison or preparing of the packet is necessary. With a single rule, the processing time already increases rapidly. The amount of time it takes for the packet to be prepared, comparing it to the rules and releasing it, is already between 67 and 99 milliseconds. \begin{figure}[H] \begin{center} \begin{tabular}{|l|l|l|l|l|l|} \hline & 0 rule & 1 rule & 5 rules & 10 rules & 15 rules \\ \hline \textbf{Average} & 23,81 ms & 74,94 ms & 78,81 ms & 87,07 ms & 94,63 ms \\ \textbf{Minimum} & 23 ms & 67 ms & 72 ms & 77 ms & 85 ms \\ \textbf{Maximum} & 24 ms & 99 ms & 98 ms & 115 ms & 124 ms \\ \hline \end{tabular} \end{center} \caption{Benchmark table} \label{fig:Benchmark table} \end{figure} $$ 0\ rule\ \textcolor{red}{\leftarrow 51,13 ms \rightarrow}\ 1\ rule\ \textcolor{teal}{\leftarrow 3,87 ms \rightarrow}\ 5\ rules\ \textcolor{orange}{\leftarrow 8,26 ms \rightarrow}\ 10\ rules\ \textcolor{orange}{\leftarrow 7,56 ms \rightarrow}\ 15\ rules $$