diff --git a/README.md b/README.md
index d9a58a1..075e7a0 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ For example, when it receives network packets:
* **masscanned** answers to `TCP SYN` (any port) with `TCP SYN/ACK` on any port,
* **masscanned** answers to `HTTP` requests (any verb) over `TCP/UDP` (any port) with a `HTTP 401` web page.
-
+
**Masscanned** currently supports most common protocols at layers 2-3-4, and a few application
protocols:
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..5128596
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,19 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS =
+SPHINXBUILD = sphinx-build
+SOURCEDIR = .
+BUILDDIR = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644
index 0000000..bbedcb5
--- /dev/null
+++ b/doc/conf.py
@@ -0,0 +1,53 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# http://www.sphinx-doc.org/en/master/config
+
+from ast import literal_eval
+import configparser
+import os
+
+# -- Path setup --------------------------------------------------------------
+
+# -- Project information -----------------------------------------------------
+
+project = "IVRE"
+copyright = "2021, The IVRE project"
+html_logo = "img/logo.png"
+master_doc = "index"
+
+def parse_cargo():
+ config = configparser.ConfigParser()
+ config.read(os.path.join("..", "Cargo.toml"))
+ if "package" not in config:
+ return None, None, None
+ package = config["package"]
+ try:
+ author = literal_eval(package.get("authors"))[0].split("<", 1)[0].strip()
+ except KeyError:
+ authors = None
+ return literal_eval(package.get("name")), author, literal_eval(package.get("version"))
+
+project, author, version = parse_cargo()
+
+# -- General configuration ---------------------------------------------------
+
+extensions = []
+
+autosectionlabel_prefix_document = True
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "sphinx_rtd_theme"
diff --git a/doc/demo.gif b/doc/img/demo.gif
similarity index 100%
rename from doc/demo.gif
rename to doc/img/demo.gif
diff --git a/doc/img/logo.png b/doc/img/logo.png
new file mode 100644
index 0000000..b9b5199
Binary files /dev/null and b/doc/img/logo.png differ
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644
index 0000000..991b3ac
--- /dev/null
+++ b/doc/index.rst
@@ -0,0 +1,35 @@
+Welcome to Masscanned documentation!
+====================================
+
+Introduction
+------------
+
+Masscanned is a low-interaction honeypot, primarily designed to help
+gather intelligence about network scanners and bots.
+
+It has been built as a companion tool for `IVRE
+`_ but can be used independently.
+
+The code is on `GitHub `_.
+
+Here is a quick demo:
+
+|demo|
+
+Status of this documentation
+----------------------------
+
+This documentation is a work in progress!
+
+Content
+-------
+
+.. toctree::
+ :maxdepth: 3
+ :caption: Usage:
+ :glob:
+
+ usage
+
+
+.. |demo| image:: img/demo.gif
diff --git a/doc/usage.rst b/doc/usage.rst
new file mode 100644
index 0000000..91e2c74
--- /dev/null
+++ b/doc/usage.rst
@@ -0,0 +1,92 @@
+Using Masscanned
+================
+
+Dedicated addresses
+~~~~~~~~~~~~~~~~~~~
+
+Masscanned is designed to handle its own IP addresses, which means
+that the host should not have those addresses configured, and
+Masscanned will answer ``ARP`` requests (or ``ICMPv6`` ``ND`` neighbor
+sollicitations).
+
+The host may have one or more (``IPv4`` and/or ``IPv6``) addresses configured
+on an interface also used by masscanned, but those addresses must be
+different from those configured to be used by masscanned.
+
+In that situation (dedicated addresses), just run:
+
+::
+
+ # masscanned -i -f
+
+where ```` is the path of a text file with one address (``IPv4``
+or ``IPv6``) per line.
+
+Addresses shared with the host
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sometimes it is desirable to have an IP address used by the host
+(*e.g.*, for administration tasks) and by masscanned (to handle all
+other incoming packets).
+
+Since this is not implemented in masscanned, a tiny hack is needed: we
+are going to run it on a ``veth`` interface.
+
+For this example, we suppose:
+
+- The interface is ``eth0``, the address is ``192.168.0.10``.
+- We want masscanned to handle all the traffic except for incoming SSH
+ connections on TCP/22 port.
+
+We create a ``veth`` pair of interfaces, on which we are going to use
+the 0.255.0.0/31 network (which should not be a problem since
+0.0.0.0/8 is reserved as "Current Network"):
+
+::
+
+ # ip link add to_masscanned type veth peer masscanned
+ # ip link set masscanned up
+ # ip link set to_masscanned up
+ # ip addr add 0.255.0.0/31 dev to_masscanned
+ # masscanned -i masscanned
+
+Masscanned can now be used, but only from the host where it runs:
+
+::
+
+ # ping -c 1 0.255.0.1
+ PING 0.255.0.1 (0.255.0.1) 56(84) octets de données.
+ 64 octets de 0.255.0.1 : icmp_seq=1 ttl=64 temps=0.442 ms
+
+ --- statistiques ping 0.255.0.1 ---
+ 1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
+ rtt min/avg/max/mdev = 0.442/0.442/0.442/0.000 ms
+
+Now, we are going to use Netfilter / ``iptables`` to redirect incoming
+traffic to masscanned:
+
+::
+
+ # sysctl -w net.ipv4.ip_forward=1
+ # iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10 -p tcp --dport 22 -j ACCEPT
+ # iptables -t nat -A PREROUTING -i eth0 -d 192.168.0.10/32 -j DNAT --to-destination 0.255.0.1
+
+And, from another host on the 192.168.0.0/24 network:
+
+::
+
+ # ping -c 1 192.168.0.10
+ PING 192.168.0.10 (192.168.0.10) 56(84) octets de données.
+ 64 octets de 192.168.0.10 : icmp_seq=1 ttl=63 temps=0.366 ms
+
+ --- statistiques ping 192.168.0.10 ---
+ 1 paquets transmis, 1 reçus, 0% packet loss, time 0ms
+ rtt min/avg/max/mdev = 0.366/0.366/0.366/0.000 ms
+
+
+The masscanned output:
+
+::
+
+ WARN - ARP-Reply to ea:c0:d6:20:0c:6a for IP 0.255.0.1
+ WARN - ICMP-Echo-Reply to ICMP-Echo-Request