Cleanup
This commit is contained in:
parent
0e3f974492
commit
cac6d98908
32 changed files with 0 additions and 3664 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "ESP32Firewall/components/arduino"]
|
||||
path = ESP32Firewall/components/arduino
|
||||
url = https://github.com/espressif/arduino-esp32.git
|
6
ESPFirewall/.gitignore
vendored
6
ESPFirewall/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
|||
.pio
|
||||
.vscode
|
||||
|
||||
lib/esp32_https_server/
|
||||
include/theSecrets.h
|
||||
include/theCerts.h
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
368
ESPFirewall/lib/Firewall/docs/.gitignore
vendored
368
ESPFirewall/lib/Firewall/docs/.gitignore
vendored
|
@ -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*
|
|
@ -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.
|
|
@ -1,9 +0,0 @@
|
|||
@book{example-book,
|
||||
title = {{Example Book}},
|
||||
author = {Lastname, Surname},
|
||||
isbn = {123456789},
|
||||
address = {Germany},
|
||||
publisher = {Goverment},
|
||||
year = {2019},
|
||||
edition = {Second}
|
||||
}
|
|
@ -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}
|
||||
}
|
|
@ -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 $$
|
|
@ -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 |
|
@ -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}
|
|
@ -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.
|
|
@ -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}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
54
tester.py
54
tester.py
|
@ -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:])
|
Reference in a new issue