This commit is contained in:
Florian Hoss 2022-07-29 10:15:48 +02:00
parent 0e3f974492
commit cac6d98908
32 changed files with 0 additions and 3664 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "ESP32Firewall/components/arduino"]
path = ESP32Firewall/components/arduino
url = https://github.com/espressif/arduino-esp32.git

View file

@ -1,6 +0,0 @@
.pio
.vscode
lib/esp32_https_server/
include/theSecrets.h
include/theCerts.h

View file

@ -1,9 +0,0 @@
# IoT Firewall on ESP8266/ESP32
Student: Florian Hoss
[flhoit00@hs-esslingen.de](mailto:flhoit00@hs-esslingen.de)
Professor: Prof. Dr. rer. nat. Tobias Heer
[tobias.heer@hs-esslingen.de](mailto:tobias.heer@hs-esslingen.de)

View file

@ -1,39 +0,0 @@
This directory is intended for project header files.
A header file is a file containing C declarations and macro definitions
to be shared between several project source files. You request the use of a
header file in your project source file (C, C++, etc) located in `src` folder
by including it, with the C preprocessing directive `#include'.
```src/main.c
#include "header.h"
int main (void)
{
...
}
```
Including a header file produces the same results as copying the header file
into each source file that needs it. Such copying would be time-consuming
and error-prone. With a header file, the related declarations appear
in only one place. If they need to be changed, they can be changed in one
place, and programs that include the header file will automatically use the
new version when next recompiled. The header file eliminates the labor of
finding and changing all the copies as well as the risk that a failure to
find one copy will result in inconsistencies within a program.
In C, the usual convention is to give header files names that end with `.h'.
It is most portable to use only letters, digits, dashes, and underscores in
header file names, and at most one dot.
Read more about using header files in official GCC documentation:
* Include Syntax
* Include Operation
* Once-Only Headers
* Computed Includes
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

View file

@ -1,59 +0,0 @@
#ifndef THECERTS_H
#define THECERTS_H
#include "pgmspace.h"
const char cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIDSzCCAjMCCQD2ahcfZAwXxDANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC
VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU9yYW5nZSBDb3VudHkx
EDAOBgNVBAoMB1ByaXZhZG8xGjAYBgNVBAMMEXNlcnZlci56bGFiZWwuY29tMR8w
HQYJKoZIhvcNAQkBFhBlYXJsZUB6bGFiZWwuY29tMB4XDTE4MDMwNjA1NDg0NFoX
DTE5MDMwNjA1NDg0NFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh
dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAPVKBwbZ+KDSl40YCDkP6y8Sv4iNGvEOZg8Y
X7sGvf/xZH7UiCBWPFIRpNmDSaZ3yjsmFqm6sLiYSGSdrBCFqdt9NTp2r7hga6Sj
oASSZY4B9pf+GblDy5m10KDx90BFKXdPMCLT+o76Nx9PpCvw13A848wHNG3bpBgI
t+w/vJCX3bkRn8yEYAU6GdMbYe7v446hX3kY5UmgeJFr9xz1kq6AzYrMt/UHhNzO
S+QckJaY0OGWvmTNspY3xCbbFtIDkCdBS8CZAw+itnofvnWWKQEXlt6otPh5njwy
+O1t/Q+Z7OMDYQaH02IQx3188/kW3FzOY32knER1uzjmRO+jhA8CAwEAATANBgkq
hkiG9w0BAQsFAAOCAQEAnDrROGRETB0woIcI1+acY1yRq4yAcH2/hdq2MoM+DCyM
E8CJaOznGR9ND0ImWpTZqomHOUkOBpvu7u315blQZcLbL1LfHJGRTCHVhvVrcyEb
fWTnRtAQdlirUm/obwXIitoz64VSbIVzcqqfg9C6ZREB9JbEX98/9Wp2gVY+31oC
JfUvYadSYxh3nblvA4OL+iEZiW8NE3hbW6WPXxvS7Euge0uWMPc4uEcnsE0ZVG3m
+TGimzSdeWDvGBRWZHXczC2zD4aoE5vrl+GD2i++c6yjL/otHfYyUpzUfbI2hMAA
5tAF1D5vAAwA8nfPysumlLsIjohJZo4lgnhB++AlOg==
-----END CERTIFICATE-----
)EOF";
const char key[] PROGMEM = R"EOF(
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA9UoHBtn4oNKXjRgIOQ/rLxK/iI0a8Q5mDxhfuwa9//FkftSI
IFY8UhGk2YNJpnfKOyYWqbqwuJhIZJ2sEIWp2301OnavuGBrpKOgBJJljgH2l/4Z
uUPLmbXQoPH3QEUpd08wItP6jvo3H0+kK/DXcDzjzAc0bdukGAi37D+8kJfduRGf
zIRgBToZ0xth7u/jjqFfeRjlSaB4kWv3HPWSroDNisy39QeE3M5L5ByQlpjQ4Za+
ZM2yljfEJtsW0gOQJ0FLwJkDD6K2eh++dZYpAReW3qi0+HmePDL47W39D5ns4wNh
BofTYhDHfXzz+RbcXM5jfaScRHW7OOZE76OEDwIDAQABAoIBAQDKov5NFbNFQNR8
djcM1O7Is6dRaqiwLeH4ZH1pZ3d9QnFwKanPdQ5eCj9yhfhJMrr5xEyCqT0nMn7T
yEIGYDXjontfsf8WxWkH2TjvrfWBrHOIOx4LJEvFzyLsYxiMmtZXvy6YByD+Dw2M
q2GH/24rRdI2klkozIOyazluTXU8yOsSGxHr/aOa9/sZISgLmaGOOuKI/3Zqjdhr
eHeSqoQFt3xXa8jw01YubQUDw/4cv9rk2ytTdAoQUimiKtgtjsggpP1LTq4xcuqN
d4jWhTcnorWpbD2cVLxrEbnSR3VuBCJEZv5axg5ZPxLEnlcId8vMtvTRb5nzzszn
geYUWDPhAoGBAPyKVNqqwQl44oIeiuRM2FYenMt4voVaz3ExJX2JysrG0jtCPv+Y
84R6Cv3nfITz3EZDWp5sW3OwoGr77lF7Tv9tD6BptEmgBeuca3SHIdhG2MR+tLyx
/tkIAarxQcTGsZaSqra3gXOJCMz9h2P5dxpdU+0yeMmOEnAqgQ8qtNBfAoGBAPim
RAtnrd0WSlCgqVGYFCvDh1kD5QTNbZc+1PcBHbVV45EmJ2fLXnlDeplIZJdYxmzu
DMOxZBYgfeLY9exje00eZJNSj/csjJQqiRftrbvYY7m5njX1kM5K8x4HlynQTDkg
rtKO0YZJxxmjRTbFGMegh1SLlFLRIMtehNhOgipRAoGBAPnEEpJGCS9GGLfaX0HW
YqwiEK8Il12q57mqgsq7ag7NPwWOymHesxHV5mMh/Dw+NyBi4xAGWRh9mtrUmeqK
iyICik773Gxo0RIqnPgd4jJWN3N3YWeynzulOIkJnSNx5BforOCTc3uCD2s2YB5X
jx1LKoNQxLeLRN8cmpIWicf/AoGBANjRSsZTKwV9WWIDJoHyxav/vPb+8WYFp8lZ
zaRxQbGM6nn4NiZI7OF62N3uhWB/1c7IqTK/bVHqFTuJCrCNcsgld3gLZ2QWYaMV
kCPgaj1BjHw4AmB0+EcajfKilcqtSroJ6MfMJ6IclVOizkjbByeTsE4lxDmPCDSt
/9MKanBxAoGAY9xo741Pn9WUxDyRplww606ccdNf/ksHWNc/Y2B5SPwxxSnIq8nO
j01SmsCUYVFAgZVOTiiycakjYLzxlc6p8BxSVqy6LlJqn95N8OXoQ+bkwUux/ekg
gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk=
-----END RSA PRIVATE KEY-----
)EOF";
#endif

View file

@ -1,9 +0,0 @@
#ifndef THESECRETS_H
#define THESECRETS_H
const char *ssid = "Wifi";
const char *psk = "password";
const char *username = "username";
const char *password = "password";
#endif

View file

@ -1,368 +0,0 @@
## Core latex/pdflatex auxiliary files:
*.aux
*.lof
*.log
*.lot
*.fls
*.out
*.toc
*.fmt
*.fot
*.cb
*.cb2
.*.lb
## Intermediate documents:
*.dvi
*.xdv
*-converted-to.*
# these rules might exclude image files for figures etc.
# *.ps
# *.eps
# *.pdf
## Generated if empty string is given at "Please type another file name for output:"
*.pdf
## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl
*.bcf
*.blg
*-blx.aux
*-blx.bib
*.run.xml
## Build tool auxiliary files:
*.fdb_latexmk
*.synctex
*.synctex(busy)
*.synctex.gz
*.synctex.gz(busy)
*.pdfsync
## Build tool directories for auxiliary files
# latexrun
latex.out/
## Auxiliary and intermediate files from other packages:
# algorithms
*.alg
*.loa
# achemso
acs-*.bib
# amsthm
*.thm
# beamer
*.nav
*.pre
*.snm
*.vrb
# changes
*.soc
# comment
*.cut
# cprotect
*.cpt
# elsarticle (documentclass of Elsevier journals)
*.spl
# endnotes
*.ent
# fixme
*.lox
# feynmf/feynmp
*.mf
*.mp
*.t[1-9]
*.t[1-9][0-9]
*.tfm
#(r)(e)ledmac/(r)(e)ledpar
*.end
*.?end
*.[1-9]
*.[1-9][0-9]
*.[1-9][0-9][0-9]
*.[1-9]R
*.[1-9][0-9]R
*.[1-9][0-9][0-9]R
*.eledsec[1-9]
*.eledsec[1-9]R
*.eledsec[1-9][0-9]
*.eledsec[1-9][0-9]R
*.eledsec[1-9][0-9][0-9]
*.eledsec[1-9][0-9][0-9]R
# glossaries
*.acn
*.acr
*.glg
*.glo
*.gls
*.glsdefs
*.lzo
*.lzs
*.slg
*.slo
*.sls
# uncomment this for glossaries-extra (will ignore makeindex's style files!)
# *.ist
# gnuplot
*.gnuplot
*.table
# gnuplottex
*-gnuplottex-*
# gregoriotex
*.gaux
*.glog
*.gtex
# htlatex
*.4ct
*.4tc
*.idv
*.lg
*.trc
*.xref
# hyperref
*.brf
# knitr
*-concordance.tex
# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
# *.tikz
*-tikzDictionary
# listings
*.lol
# luatexja-ruby
*.ltjruby
# makeidx
*.idx
*.ilg
*.ind
# minitoc
*.maf
*.mlf
*.mlt
*.mtc[0-9]*
*.slf[0-9]*
*.slt[0-9]*
*.stc[0-9]*
# minted
_minted*
*.pyg
# morewrites
*.mw
# newpax
*.newpax
# nomencl
*.nlg
*.nlo
*.nls
# pax
*.pax
# pdfpcnotes
*.pdfpc
# sagetex
*.sagetex.sage
*.sagetex.py
*.sagetex.scmd
# scrwfile
*.wrt
# svg
svg-inkscape/
# sympy
*.sout
*.sympy
sympy-plots-for-*.tex/
# pdfcomment
*.upa
*.upb
# pythontex
*.pytxcode
pythontex-files-*/
# tcolorbox
*.listing
# thmtools
*.loe
# TikZ & PGF
*.dpth
*.md5
*.auxlock
# titletoc
*.ptc
# todonotes
*.tdo
# vhistory
*.hst
*.ver
# easy-todo
*.lod
# xcolor
*.xcp
# xmpincl
*.xmpi
# xindy
*.xdy
# xypic precompiled matrices and outlines
*.xyc
*.xyd
# endfloat
*.ttt
*.fff
# Latexian
TSWLatexianTemp*
## Editors:
# WinEdt
*.bak
*.sav
# Texpad
.texpadtmp
# LyX
*.lyx~
# Kile
*.backup
# gummi
.*.swp
# KBibTeX
*~[0-9]*
# TeXnicCenter
*.tps
# auto folder when using emacs and auctex
./auto/*
*.el
# expex forward references with \gathertags
*-tags.tex
# standalone packages
*.sta
# Makeindex log files
*.lpz
# xwatermark package
*.xwm
# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
# Uncomment the next line to have this generated file ignored.
#*Notes.bib
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*

View file

@ -1,170 +0,0 @@
\section{API}
\subsection{REST Endpoints} \label{api}
Managing the firewall rules can be done over a REST API\footnote{\href{https://www.ibm.com/cloud/learn/rest-apis}{What is a REST API}}. Available routes are following and can be retrieved by visiting the API (e.g. \verb|https://10.93.0.224:8080/api|) of the device.
\lstset{style=json}
\begin{lstlisting}
[
{
"endpoint": "http://10.93.0.246:8080/api/firewall/rules",
"description": "Get all Firewall Rules",
"method": "GET"
},
{
"endpoint": "http://10.93.0.246:8080/api/firewall/rules/<key>",
"description": "Get Firewall Rule by key",
"method": "GET"
},
{
"endpoint": "http://10.93.0.246:8080/api/firewall/rules",
"description": "Create Firewall Rule",
"method": "POST"
},
{
"endpoint": "http://10.93.0.246:8080/api/firewall/rules/<key>",
"description": "Update Firewall Rule by key",
"method": "PUT"
},
{
"endpoint": "http://10.93.0.246:8080/api/firewall/rules/<key>",
"description": "Delete Firewall Rule by key",
"method": "DELETE"
}
]
\end{lstlisting}
Available protocols are \verb|TCP, UDP & ALL|
Available targets are \verb|ACCEPT & DROP|
\newpage
\subsection{Get rules}
\begin{verbatim}
curl -u username:password http://10.93.0.246:8080/api/firewall/rules
\end{verbatim}
\lstset{style=json}
\begin{lstlisting}
// HTTP/1.1 200 OK
// Content-Type: application/json; charset=utf-8
// Content-Length: 109
[
{
"key": "1",
"ip": "10.93.0.211",
"port_from": "8080",
"port_to": "8080",
"protocol": "TCP",
"target": "ACCEPT"
}
]
\end{lstlisting}
\subsection{Get rule}
\begin{verbatim}
curl -u username:password http://10.93.0.246:8080/api/firewall/rules/1
\end{verbatim}
\lstset{style=json}
\begin{lstlisting}
// HTTP/1.1 200 OK
// Content-Type: application/json; charset=utf-8
// Content-Length: 107
{
"key": "1",
"ip": "10.93.0.211",
"port_from": "8080",
"port_to": "8080",
"protocol": "TCP",
"target": "ACCEPT"
}
\end{lstlisting}
\subsection{Create rule}
\begin{verbatim}
curl -X POST -u username:password \
http://10.93.0.246:8080/api/firewall/rules
?ip=10.93.0.200&port_from=10&port_to=50&protocol=UDP&target=ACCEPT
\end{verbatim}
\lstset{style=json}
\begin{lstlisting}
// HTTP/1.1 201 Created
// Content-Type: application/json; charset=utf-8
// Content-Length: 104
{
"key": "2",
"ip": "10.93.0.200",
"port_from": "10",
"port_to": "50",
"protocol": "UDP",
"target": "ACCEPT"
}
\end{lstlisting}
\subsection{Update rule}
\begin{verbatim}
curl -X PUT -u username:password \
http://10.93.0.246:8080/api/firewall/rules/2
?ip=10.93.0.100&port_from=20&port_to=100&protocol=ALL&target=DROP
\end{verbatim}
\lstset{style=json}
\begin{lstlisting}
// HTTP/1.1 200 OK
// Content-Type: application/json; charset=utf-8
// Content-Length: 103
{
"key": "2",
"ip": "10.93.0.100",
"port_from": "20",
"port_to": "100",
"protocol": "ALL",
"target": "DROP"
}
\end{lstlisting}
\subsection{Delete rule}
\begin{verbatim}
curl -X DELETE -u username:password \
http://10.93.0.246:8080/api/firewall/rules/2
\end{verbatim}
\lstset{style=json}
\begin{lstlisting}
// HTTP/1.1 200 OK
// Content-Type: application/json; charset=utf-8
// Content-Length: 36
{
"message": "firewall rule deleted"
}
\end{lstlisting}
\subsection{HTTPS} \label{https}
To connect to the ESP over HTTPS (Hypertext Transfer Protocol Secure) the Webserver can be setup to use certificates that need to be included in the constructor as seen in section \ref{authentication}. This only works for the esp8266 with the Arduino library but can be added as an external library (\verb|esp32_https_server_combat|\footnote{\href{https://github.com/fhessel/esp32_https_server_compat}{https://github.com/fhessel/esp32\_https\_server\_compat}}) for the esp32 if needed.
\newpage
\subsection{Authentication} \label{authentication}
To authenticate the API uses basic auth. Communication should therefore be encrypted to protect the process from eavesdropping. Another solution would be to setup the rules in a private network and setup the esp without the API enabled. Therefore the rules will still apply, but cannot be changed over the network. Example certificates for the esp8266 are included in the repository and encryption can be added to the esp32 as described in section \ref{https}. Setting the username and password is done via the constructor of the api class. To create the api class to setup the firewall rules a firewall class instance is needed.
\lstset{style=c++}
\begin{lstlisting}
#include "Firewall.hpp"
#include "API.hpp"
firewall = new fw::Firewall();
firewallApi = new fw::API(firewall, cert, key, username, password);
\end{lstlisting}
After this all endpoints shown in section \ref{api} will be protected.

View file

@ -1,9 +0,0 @@
@book{example-book,
title = {{Example Book}},
author = {Lastname, Surname},
isbn = {123456789},
address = {Germany},
publisher = {Goverment},
year = {2019},
edition = {Second}
}

View file

@ -1,55 +0,0 @@
@online{pio-install,
author = {PlatformIO},
title = {Installation},
urldate = {2022-03-24},
year = {2022},
url = {https://docs.platformio.org/en/latest//core/installation.html}
}
@online{pio-about,
author = {PlatformIO},
title = {What is PlatformIO},
urldate = {2022-03-24},
year = {2022},
url = {https://docs.platformio.org/en/latest//what-is-platformio.html}
}
@online{vscode-about,
author = {{Visual Studio Code}},
title = {Getting Started},
urldate = {2022-03-24},
year = {2022},
url = {https://docs.platformio.org/en/latest//what-is-platformio.html}
}
@online{eeprom-doc,
author = {Arduino},
title = {EEPROM Library},
urldate = {2022-07-19},
year = {2022},
url = {https://docs.arduino.cc/learn/built-in-libraries/eeprom}
}
@online{espressif-wifi,
author = {Espressif},
title = {Wi-Fi Driver},
urldate = {2022-07-20},
year = {2022},
url = {https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-driver}
}
@online{lwip,
author = {LwIP},
title = {LwIP},
urldate = {2022-07-25},
year = {2022},
url = {https://www.nongnu.org/lwip/2_1_x/index.html}
}
@online{arduino-esp-idf,
author = {Espressif},
title = {Arduino as an ESP-IDF component},
urldate = {2022-07-27},
year = {2022},
url = {https://espressif-docs.readthedocs-hosted.com/projects/arduino-esp32/en/latest/esp-idf_component.html}
}

View file

@ -1,265 +0,0 @@
\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 $$

View file

@ -1,57 +0,0 @@
\section{PlatformIO}
There are several ways to compile and upload code to a micro-controller. The first tests where written and compiled in the Arduino IDE\footnote{\href{https://www.arduino.cc/en/software}{https://www.arduino.cc/en/software}}. Because references and cross-platform developing was not easy with this approach, developing in this project will be done with the help of PlatformIO.
``PlatformIO is a must-have tool for professional embedded systems engineers who develop solutions on more than one specific platform. In addition, by having a decentralized architecture, PlatformIO offers both new and existing developers a quick integration path for developing commercial-ready products, and reduces the overall time-to-market. [...] The build system structure automatically tags software dependencies and applies them using a modular hierarchy that takes away the usual complexity and pain. Developers no longer have to manually find and assemble an environment of tool-chains, compilers, and library dependencies to develop applications for a specific target. With PlatformIO, clicking the compile button will bring in all necessary dependencies automatically.''
\cite[cf.][]{pio-about}
\subsubsection{Install}
A dependency of PlatformIO is Python\footnote{\href{https://www.python.org/}{https://www.python.org/}}. To install the latest version of python, follow the installation instructions on their \href{https://www.python.org/}{website}.
PlatformIO can now simply installed by downloading a script called ``get-platformio.py'' and executing it. On Apple MacOS\footnote{\href{https://en.wikipedia.org/wiki/MacOS}{https://en.wikipedia.org/wiki/MacOS}} it can simply be installed with the help of Homebrew Packages Manager\footnote{\href{https://brew.sh/}{https://brew.sh/}}.
It is integrated into Visual Studio Code (Section \ref{sec:vsc}) and can be used with the official Plugin\footnote{\href{https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide}{https://marketplace.visualstudio.com/items?itemName=platformio.platformio-ide}} (Abbildung \ref{fig:PlatformIO in VSC}).
\subsubsection{Example configuration}
The configuration can be initialized with PIO Home or directly written into a \verb|plaftormio.ini| file. Following configuration can be used for simultaneous \verb|esp8266| and \verb|ESP32| usage:
\lstset{style=platform-io}
\begin{lstlisting}
[platformio]
default_envs = esp8266
[env:esp32]
platform = espressif32
board = az-delivery-devkit-v4
framework = arduino
monitor_speed = 115200
[env:esp8266]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 115200
\end{lstlisting}
\subsection{IDE} \label{sec:vsc}
Developing and writing code for this project will be done in Visual Studio Code\footnote{\href{https://code.visualstudio.com/}{https://code.visualstudio.com/}}.
``Visual Studio Code is a lightweight but powerful source code editor which runs on your desktop and is available for Windows, macOS and Linux. It comes with built-in support for JavaScript, TypeScript and Node.js and has a rich ecosystem of extensions for other languages (such as C++, C\#, Java, Python, PHP, Go) and runtimes (such as .NET and Unity).''
\cite[][]{vscode-about}
\begin{figure}[H]
\begin{center}
\includegraphics[width=0.3\textwidth]{pio}
\caption{PlatformIO in VSC}
\label{fig:PlatformIO in VSC}
\end{center}
\end{figure}
\subsection{Documentation}
Documentation is written in \LaTeX and will be added to the library as source-code only.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View file

@ -1,131 +0,0 @@
\documentclass[
a4paper,
oneside,
parskip=half,
listof=entryprefix,
listof=totoc,
index=totoc,
bibliography=totoc
]{scrartcl}
\usepackage{silence}
\WarningFilter{biblatex}{File 'british-iso.lbx'}
\WarningFilter{biblatex}{'\mainlang'}
\usepackage[utf8]{inputenc}
\usepackage[british]{babel}
\usepackage[T1]{fontenc}
\usepackage{pdfpages,graphicx,subcaption,lastpage}
\graphicspath{ {./images} }
\usepackage{geometry}
\geometry{a4paper, top=2.5cm, left=2.5cm, right=2.5cm, bottom=2.5cm}
\usepackage{float,listings,xcolor,csquotes,microtype,scrlayer-scrpage,etoolbox}
\usepackage[official]{eurosym}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\lstdefinestyle{c++}{
language=C++,
directivestyle={\color{codepurple}},
emph={firewall_rule_t,uint8_t,uint16_t,firewall_protocol_t,firewall_target_t,fw,Firewall,API},
emphstyle={\color{codegreen}},
}
\lstdefinestyle{shell}{
emph={cd,mkdir,git},
emphstyle={\color{codepurple}},
}
\lstdefinestyle{platform-io}{
emph={platformio,esp32,esp8266,arduino},
emphstyle={\color{codegreen}},
}
\definecolor{delim}{RGB}{20,105,176}
\definecolor{numb}{RGB}{106, 109, 32}
\definecolor{string}{rgb}{0.64,0.08,0.08}
\lstdefinestyle{json}{
rulecolor=\color{black},
showspaces=false,
showtabs=false,
breaklines=true,
postbreak=\raisebox{0ex}[0ex][0ex]{\ensuremath{\color{gray}\hookrightarrow\space}},
breakatwhitespace=true,
basicstyle=\ttfamily\small,
upquote=true,
morestring=[b]",
stringstyle=\color{string},
literate=
*{0}{{{\color{numb}0}}}{1}
{1}{{{\color{numb}1}}}{1}
{2}{{{\color{numb}2}}}{1}
{3}{{{\color{numb}3}}}{1}
{4}{{{\color{numb}4}}}{1}
{5}{{{\color{numb}5}}}{1}
{6}{{{\color{numb}6}}}{1}
{7}{{{\color{numb}7}}}{1}
{8}{{{\color{numb}8}}}{1}
{9}{{{\color{numb}9}}}{1}
{\{}{{{\color{delim}{\{}}}}{1}
{\}}{{{\color{delim}{\}}}}}{1}
{[}{{{\color{delim}{[}}}}{1}
{]}{{{\color{delim}{]}}}}{1},
}
\setuptoc{toc}{totoc}
\usepackage[
backend=biber,
urldate=long,
style=iso-authoryear,
natbib=true,
useauthor=true,
mincitenames=1,
maxcitenames=3
]{biblatex}
\addbibresource{bib/online.bib}
\addbibresource{bib/book.bib}
\DeclareNameAlias{default}{family-given/given-family}
\renewcommand*{\finalnamedelim}{\addspace{}und\space}
\AtEveryCite{
\renewcommand*{\multinamedelim}{,\space}
\renewcommand*{\nameyeardelim}{\space}
}
\AtBeginBibliography{
\renewcommand*{\multinamedelim}{,\space}
}
\AfterTOCHead[lof]{\appto\autodot{:}}
\definecolor{must-have-brown}{RGB}{51, 22, 18}
\definecolor{should-have-red}{RGB}{229, 24, 31}
\definecolor{could-have-orange}{RGB}{246, 121, 28}
\definecolor{wont-have-blue}{RGB}{32, 169, 215}
\newcommand{\morequirement}[1]{\paragraph{#1}\hfill\textbf{\textcolor{must-have-brown}{must-have}}\\}
\newcommand{\srequirement}[1]{\paragraph{#1}\hfill\textbf{\textcolor{should-have-red}{should-have}}\\}
\newcommand{\corequirement}[1]{\paragraph{#1}\hfill\textbf{\textcolor{could-have-orange}{could-have}}\\}
\newcommand{\wrequirement}[1]{\paragraph{#1}\hfill\textbf{\textcolor{wont-have-blue}{won't-have}}\\}
\ihead{IoT Firewall - ESP8266/ESP32}
\chead{}
\ohead{Florian Hoss}
\ofoot{Page~\thepage{}/\pageref{LastPage}}
\cfoot{}
\title{IoT Firewall - ESP8266/ESP32}
\usepackage[breaklinks,colorlinks,linkcolor=black,citecolor=black,filecolor=black,urlcolor=black]{hyperref}
\begin{document}
\include{titlepage/titlepage}
\tableofcontents
\newpage
\listoffigures
\include{firststeps/firststeps}
\include{storage/storage}
\include{firewall/firewall}
\include{api/api}
\printbibliography[title=Bibliography]
\end{document}

View file

@ -1,32 +0,0 @@
\section{Storage}
To store the firewall rules there will be a class to handle storing and retrieving of the currently active rules. For each board there is a library available that will handle most of the work.
\subsection{esp8266}
A library called EEPROM can be used to access the memory that will be kept when the board is powered off. With this library we can easily write and read from this area.
``The supported micro-controllers on the various Arduino [...] boards have different amounts of EEPROM: [...] The Arduino [...] boards have an emulated EEPROM space of 1024 bytes.''
\cite[][]{eeprom-doc}
\subsection{esp32}
To save and retrieve data on the esp32, the Preferences.h\footnote{\href{https://github.com/espressif/arduino-esp32/blob/master/libraries/Preferences/src/Preferences.h}{preferences.h on github}} library will be used. It is a replacement for the deprecated EEPROM library that is used with the esp8266.
\subsection{Storage class}
To use the individual library with the firewall rules the firewall can be extended with the storage class. Following protected methods will be available to store and retrieve firewall rules:
\lstset{style=c++}
\begin{lstlisting}
uint8_t retrieve_amount_of_rules();
void store_amount_of_rules(const uint8_t new_amount);
firewall_rule_t *retrieve_firewall_rule(const uint8_t key);
void store_all_firewall_rules(firewall_rule_t *rule_head);
void store_firewall_rule(firewall_rule_t *rule_ptr);
\end{lstlisting}
\subsection{Space Allocation}
Each rule will get exactly the space it needs (\verb|firewall_rule_t|). Therefore a maximum of rules of \textbf{15} is specified in the constructor of the class. At the lowest storage space, there will be an 8 bit class variable for the current amount of rules, to keep track how many rules need to be restored at power up.

View file

@ -1,30 +0,0 @@
\newcommand{\HRule}[2]{\noindent\rule[#1]{\linewidth}{#2}}
\newcommand{\vlinespace}[1]{\vspace*{#1\baselineskip}}
\newcommand{\titleemph}[1]{\textbf{#1}}
\begin{titlepage}
\sffamily
\hfill
\includegraphics[width=5cm]{hs-esslingen}
\HRule{13pt}{1pt}
\centering
\Large
\vlinespace{5}\\
Student research project\\
\huge
\textbf{IoT Firewall\\for ESP8266/ESP32}\\
\Large
\vlinespace{5}
Software Engineering course\\
of the Faculty of Information Technology\\
in the 6th semester\\
\vlinespace{6}
\huge
\textbf{Florian Hoss}\\
\Large
\vfill
\raggedright{}
\HRule{13pt}{1pt} \\
\titleemph{Git:} \href{https://github.com/flohoss/esp8266\_firewall\_api}{https://github.com/flohoss/esp8266\_firewall\_api} \\
\titleemph{Git:} \href{https://github.com/flohoss/esp32\_firewall\_api}{https://github.com/flohoss/esp32\_firewall\_api} \\
\titleemph{Professor:} Prof. Dr. rer. nat. Tobias Heer
\end{titlepage}

View file

@ -1,276 +0,0 @@
#include "API.hpp"
namespace fw
{
API::API(fw::Firewall *firewall, const char *cert, const char *key, const char *username, const char *password, const String ip, const uint16_t port)
{
this->firewall = firewall;
this->api_ip = ip;
this->api_port = port;
if (this->setup_auth(username, password) == ERROR)
endless_loop();
#ifdef ESP32
this->server = new WebServer(port);
#elif defined(ESP8266)
this->server = new ESP8266WebServerSecure(port);
this->serverCache = new ServerSessions(5);
#endif
this->setup_routing(cert, key);
this->server->begin();
Serial.printf("%s listening on port %i\n", log, port);
}
API::~API()
{
this->server->stop();
}
void API::handle_client()
{
this->server->handleClient();
}
String API::get_url_base()
{
#ifdef ESP32
return "http://" + this->api_ip + ":" + this->api_port;
#elif defined(ESP8266)
return "https://" + this->api_ip + ":" + this->api_port;
#endif
}
ok_t API::setup_auth(const char *username, const char *password)
{
if (!username || *username == 0x00 || strlen(username) > CREDENTIALS_LENGTH)
{
Serial.printf("%s Username too long or missing!\n", log);
return ERROR;
}
strncpy(credentials.username, username, CREDENTIALS_LENGTH);
if (!password || *password == 0x00 || strlen(password) > CREDENTIALS_LENGTH)
{
Serial.printf("%s Password too long or missing!\n", log);
return ERROR;
}
strncpy(credentials.password, password, CREDENTIALS_LENGTH);
return SUCCESS;
}
auth_t API::check_auth()
{
if (server->authenticate(this->credentials.username, this->credentials.password))
{
return AUTHENTICATED;
}
else
{
this->json_message_response("unauthorised", 403);
return DENIED;
}
}
void API::setup_routing(const char *cert, const char *key)
{
#ifdef ESP8266
this->server->getServer().setRSACert(new BearSSL::X509List(cert), new BearSSL::PrivateKey(key));
this->server->getServer().setCache(serverCache);
#endif
this->server->on("/api/firewall/rules", HTTP_GET, std::bind(&API::get_firewall_rules_handler, this));
this->server->on(UriRegex("/api/firewall/rules/([0-9]+)"), HTTP_GET, std::bind(&API::get_firewall_rule_handler, this));
this->server->on("/api/firewall/rules", HTTP_POST, std::bind(&API::post_firewall_handler, this));
this->server->on(UriRegex("/api/firewall/rules/([0-9]+)"), HTTP_DELETE, std::bind(&API::delete_firewall_handler, this));
this->server->on("/api", HTTP_GET, std::bind(&API::get_endpoint_list_handler, this));
this->server->onNotFound(std::bind(&API::not_found_handler, this));
add_endpoint_to_list("/api/firewall/rules", "GET", "Get all Firewall Rules");
add_endpoint_to_list("/api/firewall/rules/<key>", "GET", "Get Firewall Rule by key");
add_endpoint_to_list("/api/firewall/rules", "POST", "Create Firewall Rule");
add_endpoint_to_list("/api/firewall/rules/<key>", "DELETE", "Delete Firewall Rule by key");
}
void API::add_endpoint_to_list(const String uri, const char *method, const char *description)
{
api_endpoint_t *temp;
const String url = get_url_base() + uri;
api_endpoint_t *api_ptr = (api_endpoint_t *)malloc(sizeof(api_endpoint_t));
strncpy(api_ptr->uri, url.c_str(), sizeof(api_ptr->uri));
strncpy(api_ptr->method, method, sizeof(api_ptr->method));
strncpy(api_ptr->description, description, sizeof(api_ptr->description));
if (this->endpoint_head == NULL)
{
this->endpoint_head = api_ptr;
api_ptr->next = NULL;
return;
}
temp = this->endpoint_head;
while (temp->next != NULL)
{
temp = temp->next;
}
temp->next = api_ptr;
api_ptr->next = NULL;
return;
}
void API::not_found_handler()
{
this->json_message_response("see " + get_url_base() + "/api for available routes", 404);
}
void API::get_endpoint_list_handler()
{
this->json_array_response(this->construct_json_api(), 200);
}
void API::get_firewall_rule_handler()
{
if (this->check_auth() == DENIED)
return;
String param = this->server->pathArg(0);
int rule_number = atoi(param.c_str());
firewall_rule_t *rule_ptr = firewall->get_rule_from_firewall(rule_number);
if (rule_ptr == NULL)
this->json_message_response("rule does not exist", 404);
else
this->json_generic_response(this->construct_json_firewall_rule(rule_ptr), 200);
}
void API::get_firewall_rules_handler()
{
if (this->check_auth() == DENIED)
return;
this->json_array_response(this->construct_json_firewall(), 200);
}
void API::post_firewall_handler()
{
if (this->check_auth() == DENIED)
return;
if (request_has_all_firewall_parameter())
{
String args[IPV4ADDRESS_LENGTH] = {};
for (uint8_t i = 0; i < firewall_fields_amount; i++)
{
args[i] = this->server->arg(firewall_fields[i]);
}
firewall_rule_t *rule_ptr = firewall->add_rule_to_firewall(args);
this->json_generic_response(this->construct_json_firewall_rule(rule_ptr), 201);
}
else
{
this->json_message_response("not enough parameter provided", 400);
}
}
void API::delete_firewall_handler()
{
if (this->check_auth() == DENIED)
return;
String param = this->server->pathArg(0);
int rule_number = atoi(param.c_str());
if (firewall->delete_rule_from_firewall(rule_number) == SUCCESS)
this->json_message_response("firewall rule deleted", 200);
else
this->json_message_response("cannot delete firewall rule", 500);
}
bool API::request_has_all_firewall_parameter()
{
if (!this->server->args())
return false;
else
{
for (uint8_t i = 0; i < firewall_fields_amount; i++)
{
if (i != KEY && !this->server->hasArg(firewall_fields[i]))
return false;
}
return true;
}
}
String API::json_new_attribute(String key, String value, bool last)
{
String json_string;
json_string += "\"" + key + "\": \"" + value + "\"";
if (!last)
json_string += ",";
return json_string;
}
String API::json_new_attribute(String key, uint32_t value, bool last)
{
return json_new_attribute(key, String(value), last);
}
void API::json_generic_response(String serialized_string, const uint16_t response_code)
{
this->server->send(response_code, json_response_type, serialized_string);
}
void API::json_array_response(String serialized_string, const uint16_t response_code)
{
this->server->send(response_code, json_response_type, "[" + serialized_string + "]");
}
void API::json_message_response(String message, const uint16_t response_code)
{
String serialized_string = "{";
serialized_string += json_new_attribute("message", message, true);
serialized_string += "}";
this->server->send(response_code, json_response_type, serialized_string);
}
String API::construct_json_firewall_rule(firewall_rule_t *rule_ptr)
{
String serialized_string = "{";
serialized_string += json_new_attribute(firewall_fields[KEY], rule_ptr->key);
serialized_string += json_new_attribute(firewall_fields[IP], rule_ptr->ip);
serialized_string += json_new_attribute(firewall_fields[PORT_FROM], rule_ptr->port_from);
serialized_string += json_new_attribute(firewall_fields[PORT_TO], rule_ptr->port_to);
serialized_string += json_new_attribute(firewall_fields[PROTOCOL], protocol_to_string(rule_ptr->protocol));
serialized_string += json_new_attribute(firewall_fields[TARGET], target_to_string(rule_ptr->target), true);
serialized_string += "}";
return serialized_string;
}
String API::construct_json_firewall()
{
firewall_rule_t *rule_ptr = firewall->get_rule_head();
String serialized_string;
while (rule_ptr != NULL)
{
serialized_string += construct_json_firewall_rule(rule_ptr);
rule_ptr = rule_ptr->next;
if (rule_ptr != NULL)
serialized_string += ",";
}
return serialized_string;
}
String API::construct_json_api_endpoint(api_endpoint_t *api_ptr)
{
String serialized_string = "{";
serialized_string += json_new_attribute("endpoint", api_ptr->uri);
serialized_string += json_new_attribute("description", api_ptr->description);
serialized_string += json_new_attribute("method", api_ptr->method, true);
serialized_string += "}";
return serialized_string;
}
String API::construct_json_api()
{
api_endpoint_t *api_ptr = this->endpoint_head;
String serialized_string;
while (api_ptr != NULL)
{
serialized_string += construct_json_api_endpoint(api_ptr);
api_ptr = api_ptr->next;
if (api_ptr != NULL)
serialized_string += ",";
}
return serialized_string;
}
}

View file

@ -1,65 +0,0 @@
#ifndef ESP32_API_HPP
#define ESP32_API_HPP
#ifdef ESP32
#include "WebServer.h"
#elif defined(ESP8266)
#include "ESP8266WebServerSecure.h"
#endif
#include "uri/UriRegex.h"
#include "Firewall.hpp"
#include "Utils.hpp"
namespace fw
{
class API
{
public:
API(Firewall *, const char *cert, const char *key, const char *username, const char *password, const String ip, const uint16_t port = 8080);
~API();
void handle_client();
private:
#ifdef ESP32
WebServer *server;
#elif defined(ESP8266)
BearSSL::ESP8266WebServerSecure *server;
BearSSL::ServerSessions *serverCache;
#endif
Firewall *firewall;
credential_t credentials;
api_endpoint_t *endpoint_head = NULL;
String api_ip = "0.0.0.0";
uint16_t api_port;
String json_response_type = "application/json; charset=utf-8";
String log = "[API]";
String get_url_base();
ok_t setup_auth(const char *username, const char *password);
auth_t check_auth();
void setup_routing(const char *cert, const char *key);
void add_endpoint_to_list(const String uri, const char *method, const char *description);
void not_found_handler();
void get_endpoint_list_handler();
void get_firewall_rule_handler();
void get_firewall_rules_handler();
void post_firewall_handler();
void delete_firewall_handler();
bool request_has_all_firewall_parameter();
String json_new_attribute(String key, String value, bool last = false);
String json_new_attribute(String key, uint32_t value, bool last = false);
void json_generic_response(String serialized_string, const uint16_t response_code);
void json_array_response(String serialized_string, const uint16_t response_code);
void json_message_response(String message, const uint16_t response_code);
String construct_json_firewall_rule(firewall_rule_t *rule_ptr);
String construct_json_firewall();
String construct_json_api_endpoint(api_endpoint_t *api_ptr);
String construct_json_api();
};
}
#endif

View file

@ -1,133 +0,0 @@
#include "Firewall.hpp"
namespace fw
{
Firewall::Firewall()
{
this->amount_of_rules = retrieve_amount_of_rules();
for (uint8_t i = 1; i <= this->amount_of_rules; i++)
{
firewall_rule_t *rule_ptr = retrieve_firewall_rule(i);
this->add_rule_to_firewall(rule_ptr, false);
}
}
Firewall::~Firewall()
{
}
firewall_rule_t *Firewall::get_rule_head()
{
return this->rule_head;
}
void Firewall::add_rule_to_firewall(firewall_rule_t *rule_ptr, const bool save_in_eeprom)
{
store_amount_of_rules(this->amount_of_rules);
if (save_in_eeprom)
Storage::store_firewall_rule(rule_ptr);
if (this->rule_head == NULL)
{
this->rule_head = rule_ptr;
rule_ptr->next = NULL;
return;
}
firewall_rule_t *current_rule;
current_rule = this->rule_head;
while (current_rule->next != NULL)
current_rule = current_rule->next;
current_rule->next = rule_ptr;
rule_ptr->next = NULL;
}
firewall_rule_t *Firewall::add_rule_to_firewall(String *args)
{
firewall_rule_t *rule_ptr = (firewall_rule_t *)malloc(sizeof(firewall_rule_t));
rule_ptr->key = ++this->amount_of_rules;
strncpy(rule_ptr->ip, args[IP].c_str(), sizeof(rule_ptr->ip));
rule_ptr->port_from = args[PORT_FROM].toInt();
rule_ptr->port_to = args[PORT_TO].toInt();
rule_ptr->protocol = string_to_protocol(args[PROTOCOL]);
rule_ptr->target = string_to_target(args[TARGET]);
add_rule_to_firewall(rule_ptr);
return rule_ptr;
}
firewall_rule_t *Firewall::get_rule_from_firewall(const uint8_t key)
{
firewall_rule_t *rule_ptr = this->rule_head;
if (this->rule_head == NULL)
return NULL;
while (rule_ptr->key != key)
{
if (rule_ptr->next == NULL)
return NULL;
else
rule_ptr = rule_ptr->next;
}
return rule_ptr;
}
ok_t Firewall::delete_rule_from_firewall(const uint8_t key)
{
if (this->rule_head == NULL)
return NO_ACTION;
firewall_rule_t *current_rule = this->rule_head;
firewall_rule_t *previous_rule = NULL;
firewall_rule_t *temp = NULL;
while (current_rule->key != key)
{
if (current_rule->next == NULL)
return NO_ACTION;
else
{
previous_rule = current_rule;
current_rule = current_rule->next;
}
}
if (current_rule == this->rule_head)
{
this->rule_head = rule_head->next;
temp = this->rule_head;
}
else
{
previous_rule->next = current_rule->next;
temp = previous_rule->next;
}
while (temp != NULL)
{
temp->key--;
temp = temp->next;
}
free(current_rule);
this->amount_of_rules--;
Storage::store_amount_of_rules(this->amount_of_rules);
if (this->amount_of_rules != 0)
Storage::store_all_firewall_rules(rule_head);
return SUCCESS;
}
bool Firewall::is_included_in_firewall(String &ip, const uint32_t &port)
{
firewall_rule_t *rule_ptr = this->rule_head;
while (rule_ptr != NULL)
{
if (ip == String(rule_ptr->ip) &&
is_in_range(port, rule_ptr->port_from, rule_ptr->port_to) &&
rule_ptr->target != TARGET_ACCEPT)
return true;
rule_ptr = rule_ptr->next;
}
return false;
}
bool Firewall::is_client_allowed(WiFiClient client)
{
String ip = client.remoteIP().toString();
const uint32_t port = client.remotePort();
return !is_included_in_firewall(ip, port);
}
}

View file

@ -1,31 +0,0 @@
#ifndef ESP32_FIREWALL_HPP
#define ESP32_FIREWALL_HPP
#include "Utils.hpp"
#include "Storage.hpp"
#include "WiFiClient.h"
namespace fw
{
class Firewall : public Storage
{
public:
Firewall();
~Firewall();
firewall_rule_t *get_rule_head();
void add_rule_to_firewall(firewall_rule_t *rule_ptr, const bool save_in_eeprom = true);
firewall_rule_t *add_rule_to_firewall(String *args);
firewall_rule_t *get_rule_from_firewall(const uint8_t key);
ok_t delete_rule_from_firewall(const uint8_t key);
bool is_included_in_firewall(String &ip, const uint32_t &port);
bool is_client_allowed(WiFiClient client);
protected:
uint8_t amount_of_rules = 0;
firewall_rule_t *rule_head = NULL;
};
}
#endif

View file

@ -1,124 +0,0 @@
#include "Storage.hpp"
namespace fw
{
Storage::Storage()
{
#if defined(ESP8266)
this->max_rules = 15;
this->eeprom_amount_of_rules = 0;
this->eeprom_rules_head = 1;
this->eeprom_size = this->max_rules * sizeof(firewall_rule_t) + eeprom_rules_head;
EEPROM.begin(this->eeprom_size);
#endif
}
Storage::~Storage()
{
}
uint16_t Storage::eeprom_rule_position(uint8_t key)
{
#ifdef ESP32
return 0;
#elif defined(ESP8266)
return eeprom_rules_head + (key - 1) * sizeof(firewall_rule_t);
#endif
}
uint8_t Storage::retrieve_amount_of_rules()
{
#ifdef ESP32
this->memory.begin("settings", true);
const uint8_t value = memory.getUChar("amount_of_rules", 0);
this->memory.end();
return value;
#elif defined(ESP8266)
const uint8_t amount_of_rules = EEPROM.read(this->eeprom_amount_of_rules);
if (amount_of_rules > this->max_rules)
return 0;
return amount_of_rules;
#endif
}
void Storage::store_amount_of_rules(const uint8_t new_amount)
{
#ifdef ESP32
this->memory.begin("settings", false);
this->memory.putUChar("amount_of_rules", new_amount);
this->memory.end();
#elif defined(ESP8266)
EEPROM.put(this->eeprom_amount_of_rules, new_amount);
EEPROM.commit();
#endif
}
firewall_rule_t *Storage::retrieve_firewall_rule(const uint8_t key)
{
firewall_rule_t *rule_ptr = (firewall_rule_t *)malloc(sizeof(firewall_rule_t));
rule_ptr->key = key;
#ifdef ESP32
char rulename[9]; // fwRule99\n
sprintf(rulename, "fwRule%i", key);
this->memory.begin(rulename, true);
strncpy(rule_ptr->ip, this->memory.getString(firewall_fields[IP], "0.0.0.0").c_str(), sizeof(rule_ptr->ip));
rule_ptr->port_from = this->memory.getULong(firewall_fields[PORT_FROM], 0);
rule_ptr->port_to = this->memory.getULong(firewall_fields[PORT_TO], 0);
rule_ptr->protocol = static_cast<firewall_protocol_t>(this->memory.getUChar(firewall_fields[PROTOCOL], PROTOCOL_ALL));
rule_ptr->target = static_cast<firewall_target_t>(this->memory.getUChar(firewall_fields[TARGET], TARGET_REJECT));
this->memory.end();
#elif defined(ESP8266)
uint16_t eespom_position = eeprom_rule_position(key);
EEPROM.get(eespom_position, rule_ptr->ip);
EEPROM.get(eespom_position += sizeof(rule_ptr->ip), rule_ptr->port_from);
EEPROM.get(eespom_position += sizeof(rule_ptr->port_from), rule_ptr->port_to);
EEPROM.get(eespom_position += sizeof(rule_ptr->port_to), rule_ptr->protocol);
EEPROM.get(eespom_position += sizeof(rule_ptr->protocol), rule_ptr->target);
#endif
return rule_ptr;
}
void Storage::store_all_firewall_rules(firewall_rule_t *rule_head)
{
#ifdef ESP32
firewall_rule_t *temp = rule_head;
while (temp != NULL)
{
store_firewall_rule(temp);
temp = temp->next;
}
#endif
}
void Storage::store_firewall_rule(firewall_rule_t *rule_ptr)
{
#ifdef ESP32
char rulename[9]; // fwRule99\n
sprintf(rulename, "fwRule%i", rule_ptr->key);
this->memory.begin(rulename, false);
this->memory.putString(firewall_fields[IP], rule_ptr->ip);
this->memory.putULong(firewall_fields[PORT_FROM], rule_ptr->port_from);
this->memory.putULong(firewall_fields[PORT_TO], rule_ptr->port_to);
this->memory.putUChar(firewall_fields[PROTOCOL], rule_ptr->protocol);
this->memory.putUChar(firewall_fields[TARGET], rule_ptr->target);
this->memory.end();
#elif defined(ESP8266)
uint16_t eespom_position = eeprom_rule_position(rule_ptr->key);
EEPROM.put(eespom_position, rule_ptr->ip);
EEPROM.put(eespom_position += sizeof(rule_ptr->ip), rule_ptr->port_from);
EEPROM.put(eespom_position += sizeof(rule_ptr->port_from), rule_ptr->port_to);
EEPROM.put(eespom_position += sizeof(rule_ptr->port_to), rule_ptr->protocol);
EEPROM.put(eespom_position += sizeof(rule_ptr->protocol), rule_ptr->target);
EEPROM.commit();
#endif
}
}

View file

@ -1,41 +0,0 @@
#ifndef ESP32_STORAGE_HPP
#define ESP32_STORAGE_HPP
#ifdef ESP32
#include "Preferences.h"
#elif defined(ESP8266)
#include "EEPROM.h"
#endif
#include "Utils.hpp"
namespace fw
{
class Storage
{
public:
Storage();
~Storage();
private:
#ifdef ESP32
Preferences memory;
#elif defined(ESP8266)
uint8_t max_rules;
uint16_t eeprom_size;
uint16_t eeprom_amount_of_rules;
uint16_t eeprom_rules_head;
#endif
uint16_t eeprom_rule_position(uint8_t key);
protected:
uint8_t retrieve_amount_of_rules();
void store_amount_of_rules(const uint8_t new_amount);
firewall_rule_t *retrieve_firewall_rule(const uint8_t key);
void store_all_firewall_rules(firewall_rule_t *rule_head);
void store_firewall_rule(firewall_rule_t *rule_ptr);
};
}
#endif

View file

@ -1,62 +0,0 @@
#include "Utils.hpp"
namespace fw
{
String protocol_to_string(firewall_protocol_t &protocol)
{
switch (protocol)
{
case PROTOCOL_TCP:
return "TCP";
case PROTOCOL_UDP:
return "UDP";
default:
return "ALL";
}
}
firewall_protocol_t string_to_protocol(String &protocol)
{
if (protocol.equals("TCP"))
return PROTOCOL_TCP;
else if (protocol.equals("UDP"))
return PROTOCOL_UDP;
else
return PROTOCOL_ALL;
}
String target_to_string(firewall_target_t &target)
{
switch (target)
{
case TARGET_REJECT:
return "REJECT";
case TARGET_DROP:
return "DROP";
default:
return "ACCEPT";
}
}
firewall_target_t string_to_target(String &target)
{
if (target.equals("REJECT"))
return TARGET_REJECT;
else if (target.equals("DROP"))
return TARGET_DROP;
else
return TARGET_ACCEPT;
}
void endless_loop()
{
Serial.printf("Something went wrong. Running endless loop until fixed...");
while (true)
delay(500);
}
bool is_in_range(const uint32_t number, const uint32_t lower, const uint32_t upper)
{
return lower <= number && number <= upper;
}
}

View file

@ -1,84 +0,0 @@
#ifndef UTILS_HPP
#define UTILS_HPP
#include "Arduino.h"
#include "WString.h"
namespace fw
{
typedef enum firewall_targets : uint8_t
{
TARGET_REJECT = 0,
TARGET_DROP = 1,
TARGET_ACCEPT = 2,
} firewall_target_t;
typedef enum firewall_protocols : uint8_t
{
PROTOCOL_TCP = 0,
PROTOCOL_UDP = 1,
PROTOCOL_ALL = 255,
} firewall_protocol_t;
typedef enum ok : uint8_t
{
SUCCESS = 0,
ERROR = 1,
NO_ACTION = 2,
} ok_t;
typedef enum auth : uint8_t
{
AUTHENTICATED = 0,
DENIED = 1,
} auth_t;
static const uint8_t IPV4ADDRESS_LENGTH = 16;
typedef struct firewall_rules
{
uint8_t key;
char ip[IPV4ADDRESS_LENGTH];
uint32_t port_from; // port can be max 65565
uint32_t port_to; // range of uint16_t: 0 to 65535
firewall_protocol_t protocol;
firewall_target_t target;
struct firewall_rules *next;
} firewall_rule_t;
static const uint8_t firewall_fields_amount = 6;
const char firewall_fields[firewall_fields_amount][10] = {"key", "ip", "port_from", "port_to", "protocol", "target"};
typedef enum firewall_fields : uint8_t
{
KEY,
IP,
PORT_FROM,
PORT_TO,
PROTOCOL,
TARGET,
} firewall_fields_t;
static const uint8_t CREDENTIALS_LENGTH = 32;
typedef struct credentials
{
char password[CREDENTIALS_LENGTH];
char username[CREDENTIALS_LENGTH];
} credential_t;
typedef struct api_endpoints
{
char uri[60];
char method[7];
char description[30];
struct api_endpoints *next;
} api_endpoint_t;
String protocol_to_string(firewall_protocol_t &protocol);
firewall_protocol_t string_to_protocol(String &protocol);
String target_to_string(firewall_target_t &target);
firewall_target_t string_to_target(String &target);
String response_code_to_string(const uint16_t response_code);
void endless_loop();
bool is_in_range(const uint32_t number, const uint32_t lower, const uint32_t upper);
}
#endif

View file

@ -1,46 +0,0 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
int main (void)
{
...
}
```
PlatformIO Library Dependency Finder will find automatically dependent
libraries scanning project source files.
More information about PlatformIO Library Dependency Finder
- https://docs.platformio.org/page/librarymanager/ldf.html

View file

@ -1,24 +0,0 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = esp8266
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
[env:esp8266]
platform = espressif8266
board = d1_mini
framework = arduino
monitor_speed = 115200

View file

@ -1,71 +0,0 @@
#include "theSecrets.h"
#include "theCerts.h"
#ifdef ESP32
#include "WiFi.h"
#include "esp_wifi.h"
#elif defined(ESP8266)
#include "ESP8266WiFi.h"
#define LED 2
#endif
#include "Firewall.hpp"
#include "API.hpp"
fw::Firewall *firewall;
fw::API *firewallApi;
WiFiServer wifiServer(80);
#ifdef ESP8266
void toggleLED()
{
if (digitalRead(LED) == LOW)
digitalWrite(LED, HIGH);
else
digitalWrite(LED, LOW);
}
#endif
void setup()
{
#ifdef ESP8266
pinMode(LED, OUTPUT);
#endif
Serial.begin(115200);
Serial.println();
WiFi.begin(ssid, psk);
while (!WiFi.isConnected())
{
#ifdef ESP8266
toggleLED();
#endif
Serial.print(".");
delay(200);
}
Serial.println();
Serial.print("[WiFi] IP: ");
Serial.println(WiFi.localIP());
firewall = new fw::Firewall();
firewallApi = new fw::API(firewall, cert, key, username, password, WiFi.localIP().toString());
wifiServer.begin();
}
void loop()
{
firewallApi->handle_client();
WiFiClient client = wifiServer.available();
if (client)
{
if (firewall->is_client_allowed(client))
{
Serial.println("allowed");
client.stop();
}
else
{
Serial.println("rejected");
client.flush();
}
}
}

View file

@ -1,11 +0,0 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html

View file

@ -1,54 +0,0 @@
import socket
import sys
import argparse
message = "test"
def main(argv):
parser = argparse.ArgumentParser(
description='Send socket message to ip and port with protocol.')
parser.add_argument('-i', '--ip',
default='localhost',
dest='ip',
help='Provide destination ip. Defaults to localhost',
type=str
)
parser.add_argument('-p', '--port',
default=80,
dest='port',
help='Provide destination port. Defaults to 80',
type=int
)
parser.add_argument('-t', '--type', choices=['UDP', 'TCP'],
default='UDP',
dest='proto',
help='Provide protocol. Defaults to UDP',
type=str
)
args = parser.parse_args()
print(f'Sending message to {args.ip}:{args.port} width {args.proto}')
if args.proto == 'TCP':
sock = socket.socket()
try:
sock.connect((args.ip, args.port))
sock.send(message.encode())
print("Message sent.")
sock.close()
except:
print("Cannot send message...")
else:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
sock.sendto(message.encode('utf-8'), (args.ip, args.port))
print("Message sent.")
sock.close()
except:
print("Cannot send message...")
if __name__ == "__main__":
main(sys.argv[1:])