diff --git a/bin/netjsonconfig b/bin/netjsonconfig
index 317da7b54..c072edab5 100644
--- a/bin/netjsonconfig
+++ b/bin/netjsonconfig
@@ -56,7 +56,7 @@ output = parser.add_argument_group('output')
output.add_argument('--backend', '-b',
required=True,
- choices=['openwrt', 'openwisp', 'openvpn'],
+ choices=['openwrt', 'openwisp', 'openvpn', 'raspbian'],
action='store',
type=str,
help='Configuration backend')
@@ -169,7 +169,7 @@ method_arguments = parse_method_arguments(args.args)
backends = {
'openwrt': netjsonconfig.OpenWrt,
'openwisp': netjsonconfig.OpenWisp,
- 'openvpn': netjsonconfig.OpenVpn
+ 'raspbian': netjsonconfig.Raspbian
}
backend_class = backends[args.backend]
diff --git a/docs/source/_github.rst b/docs/source/_github.rst
deleted file mode 100644
index 3f8f505bb..000000000
--- a/docs/source/_github.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-.. raw:: html
-
-
-
-
-
diff --git a/docs/source/backends/openvpn.rst b/docs/source/backends/openvpn.rst
index 67fc83cab..2d529b76c 100644
--- a/docs/source/backends/openvpn.rst
+++ b/docs/source/backends/openvpn.rst
@@ -2,7 +2,7 @@
OpenVPN 2.3 Backend
===================
-.. include:: ../_github.rst
+
The ``OpenVpn`` backend allows to generate OpenVPN 2.3.x compatible configurations.
diff --git a/docs/source/backends/openwisp.rst b/docs/source/backends/openwisp.rst
index 33ba5805a..9726ff82b 100644
--- a/docs/source/backends/openwisp.rst
+++ b/docs/source/backends/openwisp.rst
@@ -2,7 +2,7 @@
OpenWISP 1.x Backend
====================
-.. include:: ../_github.rst
+
The OpenWISP 1.x Backend is based on the OpenWRT backend, therefore it inherits all
its features with some differences that are explained in this page.
diff --git a/docs/source/backends/openwrt.rst b/docs/source/backends/openwrt.rst
index 3ddfacbde..cb1447903 100644
--- a/docs/source/backends/openwrt.rst
+++ b/docs/source/backends/openwrt.rst
@@ -2,7 +2,7 @@
OpenWRT Backend
===============
-.. include:: ../_github.rst
+
The ``OpenWrt`` backend allows to generate OpenWRT compatible configurations.
diff --git a/docs/source/backends/raspbian.rst b/docs/source/backends/raspbian.rst
new file mode 100644
index 000000000..40f2831b3
--- /dev/null
+++ b/docs/source/backends/raspbian.rst
@@ -0,0 +1,800 @@
+================
+Raspbian Backend
+================
+
+
+
+The ``Raspbian`` backend allows to Raspbian compatible configuration files.
+
+.. warning::
+ This backend is in experimental stage: it may have bugs and it will
+ receive backward incompatible updates during the first 6 months
+ of development (starting from September 2017).
+ Early feedback and contributions are very welcome and will help
+ to stabilize the backend faster.
+
+Initialization
+--------------
+
+.. automethod:: netjsonconfig.Raspbian.__init__
+
+Render method
+-------------
+
+.. automethod:: netjsonconfig.Raspbian.render
+
+Code example:
+
+.. code-block:: python
+
+ from netjsonconfig import Raspbian
+
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ },
+ {
+ "address": "fd87::1",
+ "mask": 128,
+ "proto": "static",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ })
+ print o.render()
+
+Will return the following output::
+
+ # config: /etc/network/interfaces
+
+ auto eth0
+ iface eth0 inet static
+ address 192.168.1.1
+ netmask 255.255.255.0
+ iface eth0 inet6 static
+ address fd87::1
+ netmask 128
+
+Generate method
+---------------
+
+.. automethod:: netjsonconfig.Raspbian.generate
+
+Code example:
+
+.. code-block:: python
+
+ >>> import tarfile
+ >>> from netjsonconfig import Raspbian
+ >>>
+ >>> o = Raspbian({
+ ... "interfaces": [
+ ... {
+ ... "name": "eth0",
+ ... "type": "ethernet",
+ ... "addresses": [
+ ... {
+ ... "proto": "dhcp",
+ ... "family": "ipv4"
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... })
+ >>> stream = o.generate()
+ >>> print(stream)
+ <_io.BytesIO object at 0x7f8bc6efb620>
+ >>> tar = tarfile.open(fileobj=stream, mode='r:gz')
+ >>> print(tar.getmembers())
+ []
+
+The ``generate`` method does not write to disk but instead returns a instance of
+``io.BytesIO`` which contains a tar.gz file object.
+
+Write method
+------------
+
+.. automethod:: netjsonconfig.Raspbian.write
+
+Example:
+
+.. code-block:: python
+
+ >>> import tarfile
+ >>> from netjsonconfig import Raspbian
+ >>>
+ >>> o = Raspbian({
+ ... "interfaces": [
+ ... {
+ ... "name": "eth0",
+ ... "type": "ethernet",
+ ... "addresses": [
+ ... {
+ ... "proto": "dhcp",
+ ... "family": "ipv4"
+ ... }
+ ... ]
+ ... }
+ ... ]
+ ... })
+ >>> o.write('dhcp-router', path='/tmp/')
+
+Writes the configuration archive in ``/tmp/dhcp-router.tar.gz``
+
+General settings
+----------------
+
+The general settings reside in the ``general`` key of the
+*configuration dictionary*, which follows the
+`NetJSON General object `_ definition
+(see the link for the detailed specification).
+
+General settings example
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary*:
+
+.. code-block:: python
+
+ {
+ "general": {
+ "hostname": "RaspberryPi",
+ "timezone": "UTC"
+ }
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/hostname
+
+ RaspberryPi
+
+ # script: /scripts/general.sh
+
+ /etc/init.d/hostname.sh start
+ echo "Hostname of device has been modified"
+ timedatectl set-timezone UTC
+ echo "Timezone has changed to UTC"
+
+
+After modifying the config files run the following command to change the
+hostname::
+
+ source scripts/general.sh
+
+Network interfaces
+------------------
+
+The network interface settings reside in the ``interfaces`` key of the
+*configuration dictionary*, which must contain a list of
+`NetJSON interface objects `_
+(see the link for the detailed specification).
+
+There are 3 main type of interfaces:
+
+* **network interfaces**: may be of type ``ethernet``, ``virtual``, ``loopback`` or ``other``
+* **wireless interfaces**: must be of type ``wireless``
+* **bridge interfaces**: must be of type ``bridge``
+
+Dualstack (IPv4 & IPv6)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary*:
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.27.251.1",
+ "mask": 24
+ },
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fdb4:5f35:e8fd::1",
+ "mask": 48
+ }
+ ]
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/network/interfaces
+
+ auto eth0
+ iface eth0 inet static
+ address 10.27.251.1
+ netmask 255.255.255.0
+ iface eth0 inet6 static
+ address fdb4:5f35:e8fd::1
+ netmask 48
+
+DNS Servers and Search Domains
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+DNS servers can be set using ``dns_servers``, while search domains can be set using
+``dns_search``.
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ }
+ ]
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ],
+ "dns_servers": [
+ "10.11.12.13",
+ "8.8.8.8"
+ ],
+ "dns_search": [
+ "openwisp.org",
+ "netjson.org"],
+ }
+
+Will return the following output::
+
+ # config: /etc/network/interfaces
+
+ auto eth0
+ iface eth0 inet static
+ address 192.168.1.1
+ netmask 255.255.255.0
+
+ auto eth1
+ iface eth1 inet dhcp
+
+ # config: /etc/resolv.conf
+
+ nameserver 10.11.12.13
+ nameserver 8.8.8.8
+ search openwisp.org
+ search netjson.org
+
+DHCP IPv6 Ethernet Interface
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary*:
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/network/interfaces
+
+ auto eth0
+ iface eth0 inet6 dhcp
+
+Bridge Interfaces
+-----------------
+
+Interfaces of type ``bridge`` can contain a option that is specific for network bridges:
+
+* ``bridge_members``: interfaces that are members of the bridge
+
+.. note::
+ The bridge members must be active when creating the bridge
+
+Installing the Software
+~~~~~~~~~~~~~~~~~~~~~~~
+
+To create a bridge interface you will need to install a program called `brctl` and
+is included in `bridge-utils `_.
+You can install it using this command::
+
+ $ aptitude install bridge-utils
+
+Bridge Interface Example
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary*:
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "lan",
+ "type": "bridge",
+ "bridge_members": [
+ "eth0",
+ "eth1"
+ ],
+ "addresses": [
+ {
+ "address": "172.17.0.2",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/network/interfaces
+
+ auto lan
+ iface lan inet static
+ address 172.17.0.2
+ netmask 255.255.255.0
+ bridge_ports eth0 eth1
+
+Wireless Settings
+-----------------
+
+To use a Raspberry Pi as various we need first install the required packages.
+You can install it using this command::
+
+ $ sudo apt-get install hostapd dnsmasq
+
+* **hostapd** - The package allows you to use the wireless interface in various
+ modes
+* **dnsmasq** - The package converts the Raspberry Pi into a DHCP and DNS server
+
+Since the configuration files are not ready yet, turn the new softwares off as follows::
+
+ $ sudo systemctl stop dnsmasq
+ $ sudo systemctl stop hostapd
+
+Configure your interface
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Let us say that ``wlan0`` is our wireless interface which we will be using.
+First the standard interface handling for ``wlan0`` needs to be disabled.
+Normally the dhcpcd daemon (DHCP client) will search the network for a DHCP server
+to assign a IP address to ``wlan0`` This is disabled by editing the configuration
+file ``/etc/dhcpcd.conf``.
+Add ``denyinterfaces wlan0`` to the end of the line (but above any other added
+``interface`` lines) and save the file.
+
+
+To configure the static IP address, create a backup of the original
+``/etc/network/interfaces``. Then replace the the file with the one generated
+by the backend. Now restart the dhcpcd daemon and setup the new ``wlan0`` configuration::
+
+ sudo service dhcpcd restart
+ sudo ifdown wlan0
+ sudo ifup wlan0
+
+Configure hostapd
+~~~~~~~~~~~~~~~~~
+
+Create a new configuration file ``/etc/hostapd/hostapd.conf``. The contents of this
+configuration will be generated by the backend.
+
+You can check if your wireless service is working by running ``/usr/sbin/hostapd /etc/hostapd/hostapd.conf``.
+At this point you should be able to see your wireless network. If you try to connect
+to this network, it will authenticate but will not recieve any IP address until
+dnsmasq is setup. Use **Ctrl+C** to stop it.
+If you want the wireless service to start automatically at boot, find the line::
+
+ #DAEMON_CONF=""
+
+in ``/etc/default/hostapd`` and replace it with::
+
+ DAEMON_CONF="/etc/hostapd/hostapd.conf"
+
+Configure dnsmasq
+~~~~~~~~~~~~~~~~~
+
+By default ``/etc/dnsmasq.conf`` contains the complete documentation for how the
+file needs to be used. It is advisable to create a copy of the original ``dnsmasq.conf``.
+After creating the backup, delete the original file and create a new file ``/etc/dnsmasq.conf``
+Setup your DNS and DHCP server. Below is an example configuration file::
+
+ # Use interface wlan0
+ interface=wlan0
+ # Assign IP addresses between 172.128.1.50 and 172.128.1.150 with a 12 hour lease time
+ dhcp-range=172.128.1.50,172.128.1.150,12h
+
+Setup IPv4 Forwarding
+~~~~~~~~~~~~~~~~~~~~~
+
+We need to enable packet forwarding. Open ``/etc/sysctl.conf`` and uncomment the
+following line::
+
+ #net.ipv4.ip_forward=1
+
+After enabling IPv4 Forwarding in ``/etc/sysctl.conf`` you can run the bash script
+``/scripts/ipv4_forwarding.sh`` generated in your ``tar.gz`` file::
+
+ source scripts/ipv4_forwarding.sh
+
+This will enable IPv4 forwarding, setup a NAT between your two interfaces and save the
+iptable in ``/etc/iptables.ipv4.nat``.
+These rules must be applied everytime the Raspberry Pi is booted up. To do so open the
+`/etc/rc.local` file and just above the line ``exit 0``, add the following line::
+
+ iptables-restore < /etc/iptables.ipv4.nat
+
+Now we just need to start our services::
+
+ sudo service hostapd start
+ sudo service dnsmasq start
+
+You should now be able to connect to your wireless network setup on the Raspberry Pi
+
+Wireless access point
+~~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary* represent one of the most
+common wireless access point configuration:
+
+.. code-block:: python
+
+ {
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/hostapd/hostapd.conf
+
+ interface=wlan0
+ driver=nl80211
+ hw_mode=g
+ channel=3
+ ieee80211n=1
+ ssid=myWiFi
+
+ # config: /etc/network/interfaces
+
+ auto wlan0
+ iface wlan0 inet manual
+
+ # script: /scripts/ipv4_forwarding.sh
+
+ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+ sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+ sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+ sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+Wireless AdHoc Mode
+~~~~~~~~~~~~~~~~~~~
+
+In wireless adhoc mode, the ``bssid`` property is required.
+
+The following example:
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "ssid": "freifunk",
+ "mode": "adhoc",
+ "bssid": "02:b8:c0:00:00:00"
+ }
+ }
+ ]
+ }
+
+Will result in::
+
+ # config: /etc/network/interfaces
+
+ auto wireless
+ iface wireless inet static
+ address 172.128.1.1
+ netmask 255.255.255.0
+ wireless-channel 1
+ wireless-essid freifunk
+ wireless-mode ad-hoc
+
+WPA2 Personal (Pre-Shared Key)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The following example shows a typical wireless access
+point using *WPA2 Personal (Pre-Shared Key)* encryption:
+
+.. code-block:: python
+
+ {
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "wpa2-personal",
+ "encryption": {
+ "protocol": "wpa2_personal",
+ "cipher": "tkip+ccmp",
+ "key": "passphrase012345"
+ }
+ }
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/hostapd/hostapd.conf
+
+ interface=wlan0
+ driver=nl80211
+ hw_mode=g
+ channel=3
+ ieee80211n=1
+ ssid=wpa2-personal
+ auth_algs=1
+ wpa=2
+ wpa_key_mgmt=WPA-PSK
+ wpa_passphrase=passphrase012345
+ wpa_pairwise=TKIP CCMP
+
+ # config: /etc/network/interfaces
+
+ auto wlan0
+ iface wlan0 inet manual
+
+ # script: /scripts/ipv4_forwarding.sh
+
+ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+ sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+ sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+ sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+Radio settings
+--------------
+
+The radio settings reside in the ``radio`` key of the *configuration dictionary*,
+which must contain a list of `NetJSON radio objects `_
+(see the link for the detailed specification).
+
+Radio object extensions
+~~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to the default *NetJSON Radio object options*, the ``Raspbian`` backend
+also requires setting the following additional options for each radio in the list:
+
++--------------+---------+-----------------------------------------------+
+| key name | type | allowed values |
++==============+=========+===============================================+
+| ``protocol`` | string | 802.11a, 802.11b, 802.11g, 802.11n, 802.11ac |
++--------------+---------+-----------------------------------------------+
+
+Radio example
+~~~~~~~~~~~~~
+
+The following *configuration dictionary*:
+
+.. code-block:: python
+
+ {
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 11,
+ "channel_width": 20,
+ "tx_power": 5,
+ "country": "IT"
+ },
+ {
+ "name": "radio1",
+ "phy": "phy1",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 36,
+ "channel_width": 20,
+ "tx_power": 4,
+ "country": "IT"
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/hostapd/hostapd.conf
+
+ interface=wlan0
+ driver=nl80211
+ hw_mode=g
+ channel=11
+ ieee80211n=1
+ ssid=myWiFi
+
+ # config: /etc/network/interfaces
+
+ auto wlan0
+ iface wlan0 inet manual
+
+ # script: /scripts/ipv4_forwarding.sh
+
+ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+ sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+ sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+ sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+Static Routes
+-------------
+
+The static routes settings reside in the ``routes`` key of the *configuration dictionary*,
+which must contain a list of `NetJSON Static Route objects `_
+(see the link for the detailed specification).
+The following *configuration dictionary*:
+
+
+Static route example
+~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+ {
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet"
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "192.168.4.1/24",
+ "next": "192.168.2.2",
+ "cost": 2,
+ },
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/network/interfaces
+
+ auto eth0
+ iface eth0 inet manual
+ post-up route add -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+ pre-up route del -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+
+NTP settings
+------------
+
+The Network Time Protocol settings reside in the ``ntp`` key of the
+*configuration dictionary*, which is a custom NetJSON extension not present in
+the original NetJSON RFC.
+
+The ``ntp`` key must contain a dictionary, the allowed options are:
+
++-------------------+---------+---------------------+
+| key name | type | function |
++===================+=========+=====================+
+| ``enabled`` | boolean | ntp client enabled |
++-------------------+---------+---------------------+
+| ``enable_server`` | boolean | ntp server enabled |
++-------------------+---------+---------------------+
+| ``server`` | list | list of ntp servers |
++-------------------+---------+---------------------+
+
+NTP settings example
+~~~~~~~~~~~~~~~~~~~~
+
+The following *configuration dictionary* :
+
+.. code-block:: python
+
+ {
+ "ntp": {
+ "enabled": True,
+ "enable_server": False,
+ "server": [
+ "0.pool.ntp.org",
+ "1.pool.ntp.org",
+ "2.pool.ntp.org"
+ ]
+ }
+
+Will be rendered as follows::
+
+ # config: /etc/ntp.conf
+
+ server 0.pool.ntp.org
+ server 1.pool.ntp.org
+ server 2.pool.ntp.org
diff --git a/docs/source/general/basics.rst b/docs/source/general/basics.rst
index 902102f59..f403cf3e8 100644
--- a/docs/source/general/basics.rst
+++ b/docs/source/general/basics.rst
@@ -2,7 +2,7 @@
Basic concepts
==============
-.. include:: ../_github.rst
+
Before starting, let's quickly introduce the main concepts used in netjsonconfig:
diff --git a/docs/source/general/commandline_utility.rst b/docs/source/general/commandline_utility.rst
index 9e2a9cc08..55e07b98d 100644
--- a/docs/source/general/commandline_utility.rst
+++ b/docs/source/general/commandline_utility.rst
@@ -2,7 +2,7 @@
Command line utility
====================
-.. include:: ../_github.rst
+
netjsonconfig ships a command line utility that can be
used from the interactive shell, bash scripts or other programming
diff --git a/docs/source/general/contributing.rst b/docs/source/general/contributing.rst
index 64d5c45c7..fa3461de8 100644
--- a/docs/source/general/contributing.rst
+++ b/docs/source/general/contributing.rst
@@ -2,7 +2,7 @@
Contributing
============
-.. include:: ../_github.rst
+
Thank you for taking the time to contribute to netjsonconfig.
diff --git a/docs/source/general/goals.rst b/docs/source/general/goals.rst
index b75c8caf5..39ba6b8d9 100644
--- a/docs/source/general/goals.rst
+++ b/docs/source/general/goals.rst
@@ -1,7 +1,7 @@
Motivations and Goals
=====================
-.. include:: ../_github.rst
+
In this page we explain the goals of this project and the motivations
that led us on this path.
diff --git a/docs/source/general/running_tests.rst b/docs/source/general/running_tests.rst
index 5b77e5d46..e2eca8c38 100644
--- a/docs/source/general/running_tests.rst
+++ b/docs/source/general/running_tests.rst
@@ -2,7 +2,7 @@
Running tests
=============
-.. include:: ../_github.rst
+
Running the test suite is really straightforward!
diff --git a/docs/source/general/setup.rst b/docs/source/general/setup.rst
index a57caf594..1e046a9e5 100644
--- a/docs/source/general/setup.rst
+++ b/docs/source/general/setup.rst
@@ -2,7 +2,7 @@
Setup
=====
-.. include:: ../_github.rst
+
Install stable version from pypi
--------------------------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 110b4c96b..08583963b 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -20,7 +20,7 @@ Netjsonconfig is part of the `OpenWISP project `_.
.. image:: ./images/openwisp.org.svg
:target: http://openwisp.org
-.. include:: _github.rst
+
**netjsonconfig** is a python library that converts `NetJSON `_
*DeviceConfiguration* objects into real router configurations that can be installed
@@ -51,6 +51,7 @@ Contents:
/backends/openwrt
/backends/openwisp
/backends/openvpn
+ /backends/raspbian
/general/commandline_utility
/general/running_tests
/general/contributing
diff --git a/netjsonconfig/__init__.py b/netjsonconfig/__init__.py
index 7e5e3c872..9aa3a6a9e 100644
--- a/netjsonconfig/__init__.py
+++ b/netjsonconfig/__init__.py
@@ -3,3 +3,4 @@
from .backends.openwrt.openwrt import OpenWrt # noqa
from .backends.openwisp.openwisp import OpenWisp # noqa
from .backends.openvpn.openvpn import OpenVpn # noqa
+from .backends.raspbian.raspbian import Raspbian # noqa
diff --git a/netjsonconfig/backends/raspbian/__init__.py b/netjsonconfig/backends/raspbian/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/netjsonconfig/backends/raspbian/converters/__init__.py b/netjsonconfig/backends/raspbian/converters/__init__.py
new file mode 100644
index 000000000..6f4a7420a
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/__init__.py
@@ -0,0 +1,9 @@
+from .general import General
+from .interfaces import Interfaces
+from .ntp import Ntp
+from .wireless import Wireless
+from .dnsservers import DnsServers
+from .dnssearch import DnsSearch
+
+__all__ = ['General', 'Interfaces', 'Ntp', 'Wireless',
+ 'DnsServers', 'DnsSearch']
diff --git a/netjsonconfig/backends/raspbian/converters/base.py b/netjsonconfig/backends/raspbian/converters/base.py
new file mode 100644
index 000000000..0fe911040
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/base.py
@@ -0,0 +1,5 @@
+from ...base.converter import BaseConverter
+
+
+class RaspbianConverter(BaseConverter):
+ pass
diff --git a/netjsonconfig/backends/raspbian/converters/dnssearch.py b/netjsonconfig/backends/raspbian/converters/dnssearch.py
new file mode 100644
index 000000000..f24e932ba
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/dnssearch.py
@@ -0,0 +1,13 @@
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class DnsSearch(RaspbianConverter):
+ netjson_key = 'dns_search'
+
+ def to_intermediate(self):
+ result = []
+ dns_search = get_copy(self.netjson, self.netjson_key)
+ for domain in dns_search:
+ result.append(domain)
+ return (('dns_search', result),)
diff --git a/netjsonconfig/backends/raspbian/converters/dnsservers.py b/netjsonconfig/backends/raspbian/converters/dnsservers.py
new file mode 100644
index 000000000..df99c4dc2
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/dnsservers.py
@@ -0,0 +1,13 @@
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class DnsServers(RaspbianConverter):
+ netjson_key = 'dns_servers'
+
+ def to_intermediate(self):
+ result = []
+ dns_servers = get_copy(self.netjson, self.netjson_key)
+ for nameserver in dns_servers:
+ result.append(nameserver)
+ return (('dns_servers', result),)
diff --git a/netjsonconfig/backends/raspbian/converters/general.py b/netjsonconfig/backends/raspbian/converters/general.py
new file mode 100644
index 000000000..c14947cfb
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/general.py
@@ -0,0 +1,12 @@
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class General(RaspbianConverter):
+ netjson_key = 'general'
+
+ def to_intermediate(self):
+ result = []
+ general = get_copy(self.netjson, self.netjson_key)
+ result.append(general)
+ return (('general', result),)
diff --git a/netjsonconfig/backends/raspbian/converters/interfaces.py b/netjsonconfig/backends/raspbian/converters/interfaces.py
new file mode 100644
index 000000000..cbc31c6d6
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/interfaces.py
@@ -0,0 +1,84 @@
+from ipaddress import IPv4Interface, ip_network
+
+import six
+
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class Interfaces(RaspbianConverter):
+ netjson_key = 'interfaces'
+
+ def to_intermediate(self):
+ result = []
+ interfaces = get_copy(self.netjson, self.netjson_key)
+ for interface in interfaces:
+ result.append(self._get_interface(interface))
+ return (('interfaces', result),)
+
+ def _get_interface(self, interface):
+ new_interface = {}
+ ifname = interface.get('name')
+ iftype = interface.get('type')
+ new_interface.update({
+ 'ifname': ifname,
+ 'iftype': iftype
+ })
+ if iftype in ['ethernet', 'bridge', 'wireless']:
+ addresses = self._get_address(interface)
+ new_interface.update({
+ 'address': addresses
+ })
+ routes = get_copy(self.netjson, 'routes')
+ new_interface.update({
+ 'mac': interface.get('mac', None),
+ 'mtu': interface.get('mtu', None),
+ 'txqueuelen': interface.get('txqueuelen', None),
+ 'autostart': interface.get('autostart', True),
+ })
+ if routes:
+ route = self._get_route(routes)
+ new_interface.update({'route': route})
+ if iftype == 'wireless' and interface.get('wireless').get('mode') == 'adhoc':
+ wireless = interface.get('wireless')
+ new_interface.update({
+ 'essid': wireless.get('ssid'),
+ 'mode': wireless.get('mode')
+ })
+ if iftype == 'bridge':
+ new_interface.update({
+ 'bridge_members': interface.get('bridge_members'),
+ 'stp': interface.get('stp', False)
+ })
+ return new_interface
+
+ def _get_address(self, interface):
+ addresses = interface.get('addresses', False)
+ if addresses:
+ for address in addresses:
+ if address.get('proto') == 'static':
+ if address.get('family') == 'ipv4':
+
+ address_mask = str(address.get('address')) + '/' + str(address.get('mask'))
+ netmask = IPv4Interface(six.text_type(address_mask))
+ address['netmask'] = netmask.with_netmask.split('/')[1]
+ del address['mask']
+ if address.get('family') == 'ipv6':
+ address['netmask'] = address['mask']
+ del address['mask']
+ return addresses
+
+ def _get_route(self, routes):
+ result = []
+ for route in routes:
+ if ip_network(six.text_type(route.get('next'))).version == 4:
+ route['version'] = 4
+ destination = IPv4Interface(six.text_type(route['destination'])).with_netmask
+ dest, dest_mask = destination.split('/')
+ route['dest'] = dest
+ route['dest_mask'] = dest_mask
+ del route['destination']
+ elif ip_network(six.text_type(route.get('next'))).version == 6:
+ route['version'] = 6
+ result.append(route)
+ return routes
diff --git a/netjsonconfig/backends/raspbian/converters/ntp.py b/netjsonconfig/backends/raspbian/converters/ntp.py
new file mode 100644
index 000000000..41ed693f7
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/ntp.py
@@ -0,0 +1,14 @@
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class Ntp(RaspbianConverter):
+ netjson_key = 'ntp'
+
+ def to_intermediate(self):
+ result = []
+ ntp = get_copy(self.netjson, self.netjson_key)
+ if ntp.get('enabled', False):
+ for server in ntp.get('server'):
+ result.append(server)
+ return (('ntp', result),)
diff --git a/netjsonconfig/backends/raspbian/converters/wireless.py b/netjsonconfig/backends/raspbian/converters/wireless.py
new file mode 100644
index 000000000..921deeb85
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/converters/wireless.py
@@ -0,0 +1,113 @@
+from ....utils import get_copy
+from .base import RaspbianConverter
+
+
+class Wireless(RaspbianConverter):
+ netjson_key = 'interfaces'
+
+ def to_intermediate(self):
+ result = []
+ interfaces = get_copy(self.netjson, self.netjson_key)
+ new_interface = {}
+ for interface in interfaces:
+ if interface.get('type') == 'wireless' and interface.get('wireless').get('mode') is not 'adhoc':
+ wireless = interface.get('wireless')
+ new_interface.update({
+ 'ifname': interface.get('name'),
+ 'iftype': interface.get('type'),
+ 'ssid': wireless.get('ssid'),
+ 'radio': wireless.get('radio'),
+ 'mode': wireless.get('mode'),
+ 'hidden': wireless.get('hidden', False),
+ 'rts_threshold': wireless.get('rts_threshold', -1),
+ 'frag_threshold': wireless.get('frag_threshold', -1),
+ 'wmm': wireless.get('wmm', False),
+ 'isolate': wireless.get('isolate', False),
+ 'macfilter': wireless.get('macfilter', None),
+ 'maclist': wireless.get('maclist', None),
+ 'encryption': self._get_encryption(wireless)
+ })
+ self._update_radio(wireless, new_interface)
+ result.append(new_interface)
+ return (('wireless', result),)
+
+ def _get_hwmode(self, radio):
+ protocol = radio.get('protocol')
+ if protocol in ['802.11a', '802.11b', '802.11g']:
+ return protocol[-1:]
+ elif radio.get('channel') <= 13:
+ return 'g'
+ else:
+ return 'a'
+
+ def _update_radio(self, wireless, interface):
+ radios = get_copy(self.netjson, 'radios')
+ if radios:
+ req_radio = [radio for radio in radios if radio['name'] == wireless.get('radio')][0]
+ interface.update({
+ 'protocol': req_radio.get('protocol').replace(".", ""),
+ 'hwmode': self._get_hwmode(req_radio),
+ 'channel': req_radio.get('channel'),
+ 'channel_width': req_radio.get('channel_width')
+ })
+ if 'country' in req_radio:
+ interface.update({'country': req_radio.get('country')})
+
+ def _get_encryption(self, wireless):
+ encryption = wireless.get('encryption', None)
+ new_encryption = {}
+ if encryption is None or encryption.get('disabled', False) or encryption.get('protocol') == 'none':
+ return new_encryption
+ protocol, method = encryption.get('protocol').split("_")
+ if 'wpa' in protocol:
+ if 'personal' in method:
+ new_encryption.update({
+ 'protocol': 'wpa',
+ 'method': 'personal',
+ 'auth_algs': '1',
+ 'wpa': '1' if protocol == 'wpa' else '2',
+ 'wpa_key_mgmt': 'WPA-PSK',
+ 'wpa_passphrase': encryption.get('key'),
+ 'cipher': self._get_cipher(encryption),
+ })
+ elif method == 'enterprise':
+ if wireless.get('mode') == 'access_point':
+ new_encryption.update({
+ 'protocol': 'wpa',
+ 'method': 'enterprise',
+ 'auth_algs': '1',
+ 'wpa': '1' if protocol == 'wpa' else '2',
+ 'wpa_key_mgmt': 'WPA-EAP',
+ 'auth_server_addr': encryption.get('server'),
+ 'auth_server_port': encryption.get('port', 1812),
+ 'auth_server_shared_secret': encryption.get('key', None),
+ })
+ elif wireless.get('mode') == 'station':
+ if encryption.get('eap_type'):
+ eap_type = encryption.get('eap_type').upper()
+ new_encryption.update({'eap_type': eap_type})
+ new_encryption.update({
+ 'protocol': 'wpa',
+ 'method': 'enterprise',
+ 'wpa_pairwise': self._get_cipher(encryption),
+ 'identity': encryption.get('identity', None),
+ 'password': encryption.get('password', None),
+ 'ca_cert': encryption.get('ca_cert', None),
+ 'client_cert': encryption.get('client_cert', None),
+ 'priv_key': encryption.get('priv_key', None),
+ 'priv_key_pwd': encryption.get('priv_key_pwd', None)
+ })
+ elif 'wep' in protocol:
+ new_encryption.update({
+ 'auth_algs': 1 if method == 'open' else 2,
+ 'protocol': 'wep',
+ 'method': method,
+ 'key': encryption.get('key', None)
+ })
+ return new_encryption
+
+ def _get_cipher(self, encryption):
+ if encryption.get('cipher'):
+ return str(encryption.get('cipher').replace('+', ' ')).upper()
+ else:
+ return None
diff --git a/netjsonconfig/backends/raspbian/raspbian.py b/netjsonconfig/backends/raspbian/raspbian.py
new file mode 100644
index 000000000..19b546394
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/raspbian.py
@@ -0,0 +1,46 @@
+import re
+
+from . import converters
+from ..base.backend import BaseBackend
+from .renderer import (Hostapd, Hostname, Interfaces, MacAddrList, Ntp, Resolv,
+ Scripts, WpaSupplicant)
+from .schema import schema
+
+
+class Raspbian(BaseBackend):
+ """
+ Raspbian Backend
+ """
+ schema = schema
+ converters = [
+ converters.General,
+ converters.Interfaces,
+ converters.Wireless,
+ converters.DnsServers,
+ converters.DnsSearch,
+ converters.Ntp
+ ]
+ renderers = [
+ Hostname,
+ Hostapd,
+ MacAddrList,
+ WpaSupplicant,
+ Interfaces,
+ Resolv,
+ Ntp,
+ Scripts,
+ ]
+
+ def _generate_contents(self, tar):
+ text = self.render(files=False)
+ files_pattern = re.compile('^# config:\s|^# script:\s', flags=re.MULTILINE)
+ files = files_pattern.split(text)
+ if '' in files:
+ files.remove('')
+ for file_ in files:
+ lines = file_.split('\n')
+ file_name = lines[0]
+ text_contents = '\n'.join(lines[2:])
+ self._add_file(tar=tar,
+ name='{0}'.format(file_name),
+ contents=text_contents)
diff --git a/netjsonconfig/backends/raspbian/renderer.py b/netjsonconfig/backends/raspbian/renderer.py
new file mode 100644
index 000000000..4468032ac
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/renderer.py
@@ -0,0 +1,39 @@
+from ..base.renderer import BaseRenderer
+
+
+class RaspbianRenderer(BaseRenderer):
+ def cleanup(self, output):
+ output = output.replace(' ', '')
+ return output
+
+
+class WpaSupplicant(RaspbianRenderer):
+ pass
+
+
+class Scripts(RaspbianRenderer):
+ pass
+
+
+class Hostname(RaspbianRenderer):
+ pass
+
+
+class Hostapd(RaspbianRenderer):
+ pass
+
+
+class MacAddrList(RaspbianRenderer):
+ pass
+
+
+class Interfaces(RaspbianRenderer):
+ pass
+
+
+class Resolv(RaspbianRenderer):
+ pass
+
+
+class Ntp(RaspbianRenderer):
+ pass
diff --git a/netjsonconfig/backends/raspbian/schema.py b/netjsonconfig/backends/raspbian/schema.py
new file mode 100644
index 000000000..83d259661
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/schema.py
@@ -0,0 +1,146 @@
+from copy import deepcopy
+
+from ...schema import schema as default_schema
+from ...utils import merge_config
+from ..openwrt.timezones import timezones
+
+schema = merge_config(default_schema, {
+ "definitions": {
+ "ap_wireless_settings": {
+ "allOf": [
+ {
+ "properties": {
+ "wmm": {
+ "type": "boolean",
+ "title": "WMM (802.11e)",
+ "description": "enables WMM (802.11e) support; "
+ "required for 802.11n support",
+ "default": True,
+ "format": "checkbox",
+ "propertyOrder": 8,
+ },
+ "isolate": {
+ "type": "boolean",
+ "title": "isolate clients",
+ "description": "isolate wireless clients from one another",
+ "default": False,
+ "format": "checkbox",
+ "propertyOrder": 9,
+ },
+ "macfilter": {
+ "type": "string",
+ "title": "MAC Filter",
+ "description": "specifies the mac filter policy, \"disable\" to disable "
+ "the filter, \"allow\" to treat it as whitelist or "
+ "\"deny\" to treat it as blacklist",
+ "enum": [
+ "disable",
+ "accept",
+ "deny",
+ ],
+ "default": "disable",
+ "propertyOrder": 15,
+ },
+ "maclist": {
+ "type": "array",
+ "title": "MAC List",
+ "description": "mac addresses that will be filtered according to the policy "
+ "specified in the \"macfilter\" option",
+ "propertyOrder": 16,
+ "items": {
+ "type": "string",
+ "title": "MAC address",
+ "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
+ "minLength": 17,
+ "maxLength": 17,
+ }
+ }
+ }
+ }
+ ]
+ },
+ "radio_hwmode_11g": {
+ "properties": {
+ "hwmode": {
+ "type": "string",
+ "title": "hardware mode",
+ "readOnly": True,
+ "propertyOrder": 8,
+ "default": "11g",
+ "enum": ["11g"],
+ }
+ }
+ },
+ "radio_hwmode_11a": {
+ "properties": {
+ "hwmode": {
+ "type": "string",
+ "title": "hardware mode",
+ "readOnly": True,
+ "propertyOrder": 8,
+ "default": "11a",
+ "enum": ["11a"],
+ }
+ }
+ },
+ "radio_80211gn_settings": {
+ "allOf": [{"$ref": "#/definitions/radio_hwmode_11g"}]
+ },
+ "radio_80211an_settings": {
+ "allOf": [{"$ref": "#/definitions/radio_hwmode_11a"}]
+ },
+ "radio_80211ac_2ghz_settings": {
+ "allOf": [{"$ref": "#/definitions/radio_hwmode_11g"}]
+ },
+ "radio_80211ac_5ghz_settings": {
+ "allOf": [{"$ref": "#/definitions/radio_hwmode_11a"}]
+ },
+ },
+ "properties": {
+ "general": {
+ "properties": {
+ "timezone": {
+ "enum": list(timezones.keys()),
+ "default": "UTC",
+ }
+ }
+ },
+ "ntp": {
+ "type": "object",
+ "title": "NTP Settings",
+ "additionalProperties": True,
+ "propertyOrder": 8,
+ "properties": {
+ "server": {
+ "title": "NTP Servers",
+ "description": "NTP server candidates",
+ "type": "array",
+ "uniqueItems": True,
+ "additionalItems": True,
+ "propertyOrder": 3,
+ "items": {
+ "title": "NTP server",
+ "type": "string",
+ "format": "hostname"
+ },
+ }
+ }
+ }
+ }
+})
+schema = deepcopy(schema)
+del schema['properties']['general']['properties']['ula_prefix']
+del schema['properties']['general']['properties']['maintainer']
+del schema['properties']['general']['properties']['description']
+del schema['properties']['routes']['items']['required'][3]
+del schema['properties']['routes']['items']['properties']['cost']
+del schema['properties']['routes']['items']['properties']['source']
+
+del schema['definitions']['wireless_interface']['allOf'][0]['properties']['wireless']['oneOf'][4]
+del schema['definitions']['wireless_interface']['allOf'][0]['properties']['wireless']['oneOf'][3]
+del schema['definitions']['base_wireless_settings']['properties']['ack_distance']
+del schema['definitions']['encryption_wireless_property_ap']['properties']['encryption']['oneOf'][3]
+del schema['definitions']['ap_wireless_settings']['allOf'][4]
+del schema['definitions']['sta_wireless_settings']['allOf'][4]
+del schema['definitions']['base_radio_settings']['properties']['phy']
+del schema['definitions']['base_radio_settings']['properties']['tx_power']
diff --git a/netjsonconfig/backends/raspbian/templates/hostapd.jinja2 b/netjsonconfig/backends/raspbian/templates/hostapd.jinja2
new file mode 100644
index 000000000..1e2491574
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/hostapd.jinja2
@@ -0,0 +1,81 @@
+{% if data.wireless and data.wireless[0].mode == 'access_point' %}
+ {% for wireless in data.wireless %}
+ # config: /etc/hostapd/hostapd.conf
+
+ interface={{ wireless.ifname }}
+ driver=nl80211
+ {% if wireless.country %}
+ country={{ wireless.country }}
+ {% endif %}
+ {% if wireless.protocol == 'a' or 'b' or 'g' %}
+ hw_mode={{ wireless.hwmode }}
+ {% endif %}
+ channel={{ wireless.channel }}
+ {% if wireless.protocol == '80211n' %}
+ ieee80211n=1
+ {% endif %}
+ {% if wireless.protocol == '80211ac' %}
+ ieee80211ac=1
+ {% endif %}
+ ssid={{ wireless.ssid }}
+ {% if wireless.hidden %}
+ ignore_broadcast_ssid=1
+ {% endif %}
+ {% if wireless.rts_threshold > 0 %}
+ rts_threshold={{ wireless.rts_threshold }}
+ {% endif %}
+ {% if wireless.frag_threshold > 0 %}
+ frag_threshold={{ wireless.frag_threshold }}
+ {% endif %}
+ {% if wireless.wmm %}
+ wmm_enabled=1
+ {% endif %}
+ {% if wireless.isolate %}
+ ap_isolate=1
+ {% endif %}
+ {% if wireless.macfilter %}
+ {% if wireless.macfilter == 'deny'%}
+ macaddr_acl=0
+ deny_mac_file=/etc/hostapd.deny
+ {% elif wireless.macfilter == 'accept' %}
+ macaddr_acl=1
+ accept_mac_file=/etc/hostapd.accept
+ {% endif %}
+ {% endif %}
+ {% if wireless.encryption %}
+ auth_algs={{ wireless.encryption.auth_algs }}
+ {% if wireless.encryption.protocol == 'wpa' %}
+ wpa={{ wireless.encryption.wpa }}
+ wpa_key_mgmt={{ wireless.encryption.wpa_key_mgmt }}
+ {% if wireless.encryption.method == 'personal' %}
+ wpa_passphrase={{ wireless.encryption.wpa_passphrase }}
+ {% if wireless.encryption.cipher != 'AUTO' %}
+ {% if wireless.encryption.wpa == '1' %}
+ wpa_pairwise={{ wireless.encryption.cipher }}
+ {% endif %}
+ {% if wireless.encryption.wpa == '2' %}
+ rsn_pairwise={{ wireless.encryption.cipher }}
+ {% endif %}
+ {% endif %}
+ {% elif wireless.encryption.method == 'enterprise'%}
+ ieee8021x=1
+ eap_server=1
+ eapol_version=1
+ {% if wireless.encryption.auth_server_addr %}
+ auth_server_addr={{ wireless.encryption.auth_server_addr }}
+ {% endif %}
+ {% if wireless.encryption.auth_server_port %}
+ auth_server_port={{ wireless.encryption.auth_server_port }}
+ {% endif %}
+ {% if wireless.encryption.auth_server_shared_secret %}
+ auth_server_shared_secret={{ wireless.encryption.auth_server_shared_secret }}
+ {% endif %}
+ {% endif %}
+ {% elif wireless.encryption.protocol == 'wep' %}
+ wep_default_key=0
+ wep_key0={{ wireless.encryption.key}}
+ {% endif %}
+ {% endif %}
+
+ {% endfor %}
+{% endif %}
diff --git a/netjsonconfig/backends/raspbian/templates/hostname.jinja2 b/netjsonconfig/backends/raspbian/templates/hostname.jinja2
new file mode 100644
index 000000000..d7ae654a9
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/hostname.jinja2
@@ -0,0 +1,6 @@
+{% if data.general and data.general[0].hostname %}
+ # config: /etc/hostname
+
+ {{ data.general[0].hostname }}
+
+{% endif %}
\ No newline at end of file
diff --git a/netjsonconfig/backends/raspbian/templates/interfaces.jinja2 b/netjsonconfig/backends/raspbian/templates/interfaces.jinja2
new file mode 100644
index 000000000..bbbdae212
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/interfaces.jinja2
@@ -0,0 +1,148 @@
+{% if data.interfaces %}
+# config: /etc/network/interfaces
+
+{% endif%}
+{% for interface in data.interfaces %}
+ {% if interface.iftype in ['ethernet', 'bridge', 'wireless'] %}
+ {% if interface.address %}
+ {% if interface.autostart %}
+ auto {{ interface.ifname }}
+ {% endif %}
+ {% for address in interface.address %}
+ {% if address.proto == 'static' %}
+ {% if address.family == 'ipv4' %}
+ iface {{ interface.ifname }} inet {{ address.proto }}
+ address {{ address.address }}
+ netmask {{ address.netmask }}
+ {% if address.gateway %}
+ gateway {{ address.gateway }}
+ {% endif %}
+ {% if interface.route %}
+ {% set routes = interface.route %}
+ {% for route in routes %}
+ {% if route.version == 4%}
+ post-up route add -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ pre-up route del -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if interface.mtu %}
+ mtu {{ interface.mtu }}
+ {% endif %}
+ {% if interface.mac %}
+ hwaddress {{ interface.mac }}
+ {% endif %}
+ {% if interface.iftype == 'bridge' %}
+ bridge_ports {{ interface.bridge_members[0] }} {{ interface.bridge_members[1] }}
+ {% if interface.stp %}
+ bridge_stp {{ interface.stp }}
+ {% endif %}
+ {% endif %}
+ {% elif address.family == 'ipv6' %}
+ iface {{ interface.ifname }} inet6 {{ address.proto }}
+ address {{ address.address }}
+ netmask {{ address.netmask }}
+ {% if address.gateway %}
+ gateway {{ address.gateway }}
+ {% endif %}
+ {% if interface.route %}
+ {% set routes = interface.route %}
+ {% for route in routes %}
+ {% if route.version == 6 %}
+ up ip -6 route add {{ route.destination }} via {{ route.next }} dev eth0
+ down ip -6 route del {{ route.destination }} via {{ route.next }} dev eth0
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if interface.mtu %}
+ mtu {{ interface.mtu }}
+ {% endif %}
+ {% if interface.mac %}
+ hwaddress {{ interface.mac }}
+ {% endif %}
+ {% if interface.iftype == 'bridge' %}
+ bridge_ports {{ interface.bridge_members[0] }} {{ interface.bridge_members[1] }}
+ {% endif %}
+ {% if interface.stp %}
+ bridge_stp {{ interface.stp }}
+ {% endif %}
+ {% endif %}
+ {% elif address.proto == 'dhcp' %}
+ {% if address.family == 'ipv4'%}
+ iface {{ interface.ifname }} inet {{ address.proto }}
+ {% if interface.route %}
+ {% set routes = interface.route %}
+ {% for route in routes %}
+ {% if route.version == 4 %}
+ post-up route add -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ pre-up route del -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if interface.mtu %}
+ pre-up /sbin/ifconfig $IFACE mtu {{ interface.mtu }}
+ {% endif %}
+ {% if interface.mac %}
+ hwaddress {{ interface.mac }}
+ {% endif %}
+ {% elif address.family == 'ipv6' %}
+ iface {{ interface.ifname }} inet6 {{ address.proto }}
+ {% if interface.route %}
+ {% set routes = interface.route %}
+ {% for route in routes %}
+ {% if route.version == 6 %}
+ up ip -6 route add {{ route.destination }} via {{ route.next }} dev eth0
+ down ip -6 route del {{ route.destination }} via {{ route.next }} dev eth0
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% if interface.mtu %}
+ pre-up /sbin/ifconfig $IFACE mtu {{ interface.mtu }}
+ {% endif %}
+ {% if interface.mac %}
+ hwaddress {{ interface.mac }}
+ {% endif %}
+ {% endif %}
+ {% endif%}
+ {% endfor %}
+ {% else %}
+ {% if interface.autostart %}
+ auto {{ interface.ifname }}
+ {% endif %}
+ {% if interface.iftype in ['ethernet', 'wireless'] and interface.mode != 'adhoc' %}
+ iface {{ interface.ifname }} inet manual
+ {% if interface.route %}
+ {% set routes = interface.route %}
+ {% for route in routes %}
+ {% if route.version == 4 %}
+ post-up route add -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ pre-up route del -net {{ route.dest }} netmask {{ route.dest_mask }} gw {{ route.next }}
+ {% endif %}
+ {% if route.version == 6 %}
+ up ip -6 route add {{ route.destination }} via {{ route.next }} dev eth0
+ down ip -6 route del {{ route.destination }} via {{ route.next }} dev eth0
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ {% endif %}
+ {% if interface.iftype == 'bridge' %}
+ bridge_ports {{ interface.bridge_members[0] }} {{ interface.bridge_members[1] }}
+ {% endif %}
+ {% if interface.stp %}
+ bridge_stp {{ interface.stp }}
+ {% endif %}
+ {% if interface.mode == 'adhoc' %}
+ iface {{ interface.ifname }} inet static
+ address 172.128.1.1
+ netmask 255.255.255.0
+ wireless-channel 1
+ wireless-essid {{ interface.essid }}
+ wireless-mode ad-hoc
+ {% endif %}
+ {% endif %}
+ {% elif interface.iftype == 'loopback'%}
+ auto {{ interface.ifname }}
+ iface {{ interface.ifname }} inet loopback
+ {% endif %}
+
+{% endfor %}
diff --git a/netjsonconfig/backends/raspbian/templates/macaddrlist.jinja2 b/netjsonconfig/backends/raspbian/templates/macaddrlist.jinja2
new file mode 100644
index 000000000..0dcd6fc58
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/macaddrlist.jinja2
@@ -0,0 +1,12 @@
+{% if data.wireless and data.wireless[0].mode == 'access_point' %}
+ {% for wireless in data.wireless %}
+ {% if wireless.macfilter %}
+ # config: /etc/hostapd.{{ wireless.macfilter }}
+
+ {% for mac in wireless.maclist %}
+ {{ mac }}
+ {% endfor %}
+
+ {% endif %}
+ {% endfor %}
+{% endif %}
diff --git a/netjsonconfig/backends/raspbian/templates/ntp.jinja2 b/netjsonconfig/backends/raspbian/templates/ntp.jinja2
new file mode 100644
index 000000000..c4af56457
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/ntp.jinja2
@@ -0,0 +1,7 @@
+{% if data.ntp %}
+# config: /etc/ntp.conf
+
+{% endif %}
+{% for ntp in data.ntp %}
+ server {{ ntp }}
+{% endfor %}
diff --git a/netjsonconfig/backends/raspbian/templates/resolv.jinja2 b/netjsonconfig/backends/raspbian/templates/resolv.jinja2
new file mode 100644
index 000000000..13d8224ff
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/resolv.jinja2
@@ -0,0 +1,10 @@
+{% if data.dns_servers or data.dns_search %}
+# config: /etc/resolv.conf
+
+{% endif %}
+{% for ip in data.dns_servers %}
+ nameserver {{ ip }}
+{% endfor %}
+{% for domain in data.dns_search %}
+ search {{ domain }}
+{% endfor %}
diff --git a/netjsonconfig/backends/raspbian/templates/scripts.jinja2 b/netjsonconfig/backends/raspbian/templates/scripts.jinja2
new file mode 100644
index 000000000..e4d34c74e
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/scripts.jinja2
@@ -0,0 +1,23 @@
+{% if data.general %}
+ # script: /scripts/general.sh
+
+ {% if 'hostname' in data.general[0]%}
+ /etc/init.d/hostname.sh start
+ echo "Hostname of device has been modified"
+ {% endif %}
+ {% if 'timezone' in data.general[0] %}
+ timedatectl set-timezone {{ data.general[0].timezone }}
+ echo "Timezone has changed to {{ data.general[0].timezone }}"
+ {% endif %}
+
+{% endif %}
+{% if data.wireless %}
+ # script: /scripts/ipv4_forwarding.sh
+
+ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+ sudo iptables -A FORWARD -i eth0 -o {{ data.wireless[0].ifname }} -m state --state RELATED,ESTABLISHED -j ACCEPT
+ sudo iptables -A FORWARD -i {{ data.wireless[0].ifname }} -o eth0 -j ACCEPT
+ sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+{% endif %}
diff --git a/netjsonconfig/backends/raspbian/templates/wpasupplicant.jinja2 b/netjsonconfig/backends/raspbian/templates/wpasupplicant.jinja2
new file mode 100644
index 000000000..c9a7af6ee
--- /dev/null
+++ b/netjsonconfig/backends/raspbian/templates/wpasupplicant.jinja2
@@ -0,0 +1,49 @@
+{% if data.wireless and data.wireless[0].mode == 'station' %}
+ {% for wireless in data.wireless %}
+ # config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+ network={
+ ssid="{{ wireless.ssid }}"
+ {% if 'wpa' in wireless.encryption.protocol %}
+ {% set encryption = wireless.encryption %}
+ {% if encryption.method == 'personal' %}
+ key="{{ encryption.wpa_passphrase }}"
+ {% if encryption.wpa_key_mgmt %}
+ key_mgmt={{ encryption.wpa_key_mgmt }}
+ {% endif %}
+ {% elif wireless.encryption.method == 'enterprise' %}
+ {% if encryption.eap_type %}
+ eap={{ encryption.eap_type }}
+ {% endif %}
+ {% if encryption.identity %}
+ identity="{{ encryption.identity }}"
+ {% endif %}
+ {% if encryption.password %}
+ password="{{ encryption.password }}"
+ {% endif %}
+ {% if encryption.ca_cert %}
+ ca_cert="{{ encryption.ca_cert }}"
+ {% endif %}
+ {% if encryption.client_cert %}
+ client_cert="{{ encryption.client_cert }}"
+ {% endif %}
+ {% if encryption.priv_key %}
+ priv_key="{{ encryption.priv_key }}"
+ {% endif %}
+ {% if encryption.priv_key_pwd %}
+ priv_key_pwd="{{ encryption.priv_key_pwd }}"
+ {% endif %}
+ {% endif%}
+ {% elif 'wep' in wireless.encryption.protocol %}
+ key_mgmt=NONE
+ wep_key0="{{ wireless.encryption.key }}"
+ {% if wireless.encryption.method == 'shared' %}
+ auth_algs=shared
+ {% endif %}
+ {% else %}
+ key_mgmt=NONE
+ {% endif %}
+ }
+
+ {% endfor %}
+{% endif %}
diff --git a/tests/raspbian/__init__.py b/tests/raspbian/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/raspbian/test_backend.py b/tests/raspbian/test_backend.py
new file mode 100644
index 000000000..a1c80293b
--- /dev/null
+++ b/tests/raspbian/test_backend.py
@@ -0,0 +1,86 @@
+import os
+import tarfile
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestBackend(unittest.TestCase, _TabsMixin):
+
+ def test_generate(self):
+ o = Raspbian({
+ "general": {
+ "hostname": "test"
+ },
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ],
+ "dns_servers": [
+ "10.11.12.13",
+ "8.8.8.8"
+ ],
+ "dns_search": [
+ "netjson.org",
+ "openwisp.org"
+ ]
+ })
+ tar = tarfile.open(fileobj=o.generate(), mode='r')
+ self.assertEqual(len(tar.getmembers()), 4)
+
+ general = tar.getmember('/etc/hostname')
+ contents = tar.extractfile(general).read().decode()
+ expected = self._tabs("""test
+
+""")
+ self.assertEqual(contents, expected)
+
+ interface = tar.getmember('/etc/network/interfaces')
+ contents = tar.extractfile(interface).read().decode()
+ expected = self._tabs("""auto eth0
+iface eth0 inet static
+address 192.168.1.1
+netmask 255.255.255.0
+
+""")
+ self.assertEqual(contents, expected)
+
+ resolv = tar.getmember('/etc/resolv.conf')
+ contents = tar.extractfile(resolv).read().decode()
+ expected = self._tabs("""nameserver 10.11.12.13
+nameserver 8.8.8.8
+search netjson.org
+search openwisp.org
+""")
+ self.assertEqual(contents, expected)
+
+ script = tar.getmember('/scripts/general.sh')
+ contents = tar.extractfile(script).read().decode()
+ expected = self._tabs("""/etc/init.d/hostname.sh start
+echo "Hostname of device has been modified"
+
+""")
+ self.assertEqual(contents, expected)
+
+ def test_write(self):
+ o = Raspbian({
+ "general": {
+ "hostname": "test"
+ }
+ })
+ o.write(name='test', path='/tmp')
+ tar = tarfile.open('/tmp/test.tar.gz', mode='r')
+ self.assertEqual(len(tar.getmembers()), 2)
+ tar.close()
+ os.remove('/tmp/test.tar.gz')
diff --git a/tests/raspbian/test_hostapd.py b/tests/raspbian/test_hostapd.py
new file mode 100644
index 000000000..d6874a0e2
--- /dev/null
+++ b/tests/raspbian/test_hostapd.py
@@ -0,0 +1,545 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestHostapd(unittest.TestCase, _TabsMixin):
+
+ def test_wpa2_personal(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "wpa2-personal",
+ "encryption": {
+ "protocol": "wpa2_personal",
+ "cipher": "tkip+ccmp",
+ "key": "passphrase012345"
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=wpa2-personal
+auth_algs=1
+wpa=2
+wpa_key_mgmt=WPA-PSK
+wpa_passphrase=passphrase012345
+rsn_pairwise=TKIP CCMP
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_wpa_personal(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "wpa-personal",
+ "encryption": {
+ "protocol": "wpa_personal",
+ "cipher": "auto",
+ "key": "passphrase012345"
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=wpa-personal
+auth_algs=1
+wpa=1
+wpa_key_mgmt=WPA-PSK
+wpa_passphrase=passphrase012345
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_wpa2_enterprise_ap(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ },
+ ],
+ "interfaces": [
+ {
+ "type": "wireless",
+ "name": "wlan0",
+ "mac": "de:9f:db:30:c9:c5",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "ap-ssid-example",
+ "encryption": {
+ "protocol": "wpa2_enterprise",
+ "server": "radius.example.com",
+ "key": "the-shared-key",
+ },
+ },
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=ap-ssid-example
+auth_algs=1
+wpa=2
+wpa_key_mgmt=WPA-EAP
+ieee8021x=1
+eap_server=1
+eapol_version=1
+auth_server_addr=radius.example.com
+auth_server_port=1812
+auth_server_shared_secret=the-shared-key
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_wep_open(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "wep",
+ "encryption": {
+ "protocol": "wep_open",
+ "key": "wepkey1234567"
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=wep
+auth_algs=1
+wep_default_key=0
+wep_key0=wepkey1234567
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_wep_shared(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "wep",
+ "encryption": {
+ "protocol": "wep_shared",
+ "key": "wepkey1234567"
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=wep
+auth_algs=2
+wep_default_key=0
+wep_key0=wepkey1234567
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_encryption_disabled(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "MyNetwork",
+ "encryption": {
+ "disabled": True,
+ "protocol": "wpa2_personal",
+ "cipher": "tkip+ccmp",
+ "key": "passphrase012345"
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=MyNetwork
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_no_encryption(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "open",
+ "encryption": {"protocol": "none"}
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=open
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_macaddracl_accept(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "MyWifiAP",
+ "macfilter": "accept",
+ "maclist": [
+ "E8:94:F6:33:8C:1D",
+ "42:6c:8f:95:0f:00"
+ ]
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=MyWifiAP
+macaddr_acl=1
+accept_mac_file=/etc/hostapd.accept
+
+# config: /etc/hostapd.accept
+
+E8:94:F6:33:8C:1D
+42:6c:8f:95:0f:00
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_macaddracl_deny(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ },
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "MyWifiAP",
+ "macfilter": "deny",
+ "maclist": [
+ "E8:94:F6:33:8C:1D",
+ "42:6c:8f:95:0f:00"
+ ]
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=MyWifiAP
+macaddr_acl=0
+deny_mac_file=/etc/hostapd.deny
+
+# config: /etc/hostapd.deny
+
+E8:94:F6:33:8C:1D
+42:6c:8f:95:0f:00
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_interfaces.py b/tests/raspbian/test_interfaces.py
new file mode 100644
index 000000000..613e6c45a
--- /dev/null
+++ b/tests/raspbian/test_interfaces.py
@@ -0,0 +1,697 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestInterfaces(unittest.TestCase, _TabsMixin):
+
+ def test_no_ip(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet"
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet manual
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_multi_no_ip(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet"
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet"
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet manual
+
+auto eth1
+iface eth1 inet manual
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv4_static(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.0.0.1",
+ "mask": 28
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 10.0.0.1
+netmask 255.255.255.240
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multi_ipv4_static(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.0.0.1",
+ "mask": 28
+ }
+ ]
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.0.0.2",
+ "mask": 28
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 10.0.0.1
+netmask 255.255.255.240
+
+auto eth1
+iface eth1 inet static
+address 10.0.0.2
+netmask 255.255.255.240
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv6_static(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fe80::ba27:ebff:fe1c:5477",
+ "mask": 64
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 static
+address fe80::ba27:ebff:fe1c:5477
+netmask 64
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multi_ipv6_static(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fe80::ba27:ebff:fe1c:5477",
+ "mask": 64
+ }
+ ]
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "2001:db8:0:0:0:ff00:42:8329",
+ "mask": 64
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 static
+address fe80::ba27:ebff:fe1c:5477
+netmask 64
+
+auto eth1
+iface eth1 inet6 static
+address 2001:db8:0:0:0:ff00:42:8329
+netmask 64
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multiple_static(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.0.0.1",
+ "mask": 28
+ },
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fe80::ba27:ebff:fe1c:5477",
+ "mask": 64
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 10.0.0.1
+netmask 255.255.255.240
+iface eth0 inet6 static
+address fe80::ba27:ebff:fe1c:5477
+netmask 64
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv4_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet dhcp
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multi_ipv4_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ }
+ ]
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet dhcp
+
+auto eth1
+iface eth1 inet dhcp
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv6_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 dhcp
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multi_ipv6_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv6"
+ }
+ ]
+ },
+ {
+ "name": "eth1",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 dhcp
+
+auto eth1
+iface eth1 inet6 dhcp
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multiple_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ },
+ {
+ "proto": "dhcp",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet dhcp
+iface eth0 inet6 dhcp
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multiple_ip(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "autostart": True,
+ "addresses": [
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ },
+ {
+ "address": "192.168.2.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ },
+ {
+ "address": "fd87::1",
+ "mask": 128,
+ "proto": "static",
+ "family": "ipv6"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 192.168.1.1
+netmask 255.255.255.0
+iface eth0 inet static
+address 192.168.2.1
+netmask 255.255.255.0
+iface eth0 inet6 static
+address fd87::1
+netmask 128
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_autostart_false(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "autostart": False
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+iface eth0 inet manual
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_mtu(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "mtu": 1500,
+ "name": "eth1",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "dhcp"
+ }
+ ],
+ "type": "ethernet",
+ }
+ ],
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth1
+iface eth1 inet dhcp
+pre-up /sbin/ifconfig $IFACE mtu 1500
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_mac(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth1",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "dhcp"
+ }
+ ],
+ "type": "ethernet",
+ "mac": "52:54:00:56:46:c0"
+ }
+ ],
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth1
+iface eth1 inet dhcp
+hwaddress 52:54:00:56:46:c0
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_multiple_ip_and_dhcp(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "proto": "dhcp",
+ "family": "ipv4"
+ },
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ },
+ {
+ "address": "192.168.2.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ },
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet dhcp
+iface eth0 inet static
+address 192.168.1.1
+netmask 255.255.255.0
+iface eth0 inet static
+address 192.168.2.1
+netmask 255.255.255.0
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_interface_with_resolv(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "address": "192.168.1.1",
+ "mask": 24,
+ "proto": "static",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ],
+ "dns_servers": ["10.11.12.13", "8.8.8.8"],
+ "dns_search": ["netjson.org", "openwisp.org"],
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 192.168.1.1
+netmask 255.255.255.0
+
+# config: /etc/resolv.conf
+
+nameserver 10.11.12.13
+nameserver 8.8.8.8
+search netjson.org
+search openwisp.org
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_loopback(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "lo",
+ "type": "loopback",
+ "addresses": [
+ {
+ "address": "127.0.0.1",
+ "mask": 8,
+ "proto": "static",
+ "family": "ipv4"
+ }
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto lo
+iface lo inet loopback
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_adhoc_wireless(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "ssid": "freifunk",
+ "mode": "adhoc",
+ "bssid": "02:b8:c0:00:00:00"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet static
+address 172.128.1.1
+netmask 255.255.255.0
+wireless-channel 1
+wireless-essid freifunk
+wireless-mode ad-hoc
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_simple_bridge(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "network": "lan",
+ "name": "br-lan",
+ "type": "bridge",
+ "bridge_members": [
+ "eth0",
+ "eth1"
+ ]
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto br-lan
+bridge_ports eth0 eth1
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_complex_bridge(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "mtu": 1500,
+ "name": "brwifi",
+ "bridge_members": [
+ "wlan0",
+ "vpn.40"
+ ],
+ "addresses": [
+ {
+ "mask": 64,
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fe80::8029:23ff:fe7d:c214"
+ }
+ ],
+ "type": "bridge",
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto brwifi
+iface brwifi inet6 static
+address fe80::8029:23ff:fe7d:c214
+netmask 64
+mtu 1500
+bridge_ports wlan0 vpn.40
+
+"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_radios.py b/tests/raspbian/test_radios.py
new file mode 100644
index 000000000..caa3131da
--- /dev/null
+++ b/tests/raspbian/test_radios.py
@@ -0,0 +1,320 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestRadio(unittest.TestCase, _TabsMixin):
+
+ def test_radio_multi(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 11,
+ "channel_width": 20,
+ "tx_power": 5,
+ "country": "IT"
+ },
+ {
+ "name": "radio1",
+ "phy": "phy1",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 36,
+ "channel_width": 20,
+ "tx_power": 4,
+ "country": "IT"
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+country=IT
+hw_mode=g
+channel=11
+ieee80211n=1
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_radio_n_24ghz(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=3
+ieee80211n=1
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_radio_n_5ghz(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 36,
+ "channel_width": 20,
+ "tx_power": 3
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=a
+channel=36
+ieee80211n=1
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_radio_ac(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11ac",
+ "channel": 132,
+ "channel_width": 80,
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=a
+channel=132
+ieee80211ac=1
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_radio_a(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11a",
+ "channel": 0,
+ "channel_width": 20
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=a
+channel=0
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_radio_g(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11g",
+ "channel": 0,
+ "channel_width": 20
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "access_point",
+ "ssid": "myWiFi"
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/hostapd/hostapd.conf
+
+interface=wlan0
+driver=nl80211
+hw_mode=g
+channel=0
+ssid=myWiFi
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_resolv.py b/tests/raspbian/test_resolv.py
new file mode 100644
index 000000000..e50757cb7
--- /dev/null
+++ b/tests/raspbian/test_resolv.py
@@ -0,0 +1,62 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestResovl(unittest.TestCase, _TabsMixin):
+
+ def test_dns_server(self):
+ o = Raspbian({
+ "dns_servers": [
+ "10.254.0.1",
+ "10.254.0.2"
+ ],
+ })
+
+ expected = """# config: /etc/resolv.conf
+
+nameserver 10.254.0.1
+nameserver 10.254.0.2
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_dns_search(self):
+ o = Raspbian({
+ "dns_search": [
+ "domain.com",
+ ],
+ })
+
+ expected = """# config: /etc/resolv.conf
+
+search domain.com
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_dns_server_and_dns_search(self):
+ o = Raspbian({
+ "dns_servers": [
+ "10.11.12.13",
+ "8.8.8.8"],
+ "dns_search": [
+ "netjson.org",
+ "openwisp.org"
+ ],
+ })
+
+ expected = """# config: /etc/resolv.conf
+
+nameserver 10.11.12.13
+nameserver 8.8.8.8
+search netjson.org
+search openwisp.org
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_no_dns_server_and_dns_search(self):
+ o = Raspbian({
+ })
+
+ expected = """"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_routes.py b/tests/raspbian/test_routes.py
new file mode 100644
index 000000000..9d5e91afc
--- /dev/null
+++ b/tests/raspbian/test_routes.py
@@ -0,0 +1,238 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestStaticRoute(unittest.TestCase, _TabsMixin):
+
+ def test_ipv4_manual_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet"
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "192.168.4.1/24",
+ "next": "192.168.2.2"
+ },
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet manual
+post-up route add -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+pre-up route del -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv4_static_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "static",
+ "address": "10.0.0.1",
+ "mask": 28
+ }
+ ]
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "192.168.4.1/24",
+ "next": "192.168.2.2"
+ },
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet static
+address 10.0.0.1
+netmask 255.255.255.240
+post-up route add -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+pre-up route del -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv4_dchp_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv4",
+ "proto": "dhcp"
+ }
+ ]
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "192.168.4.1/24",
+ "next": "192.168.2.2"
+ },
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet dhcp
+post-up route add -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+pre-up route del -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv6_manual_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet"
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "fd89::1/128",
+ "next": "fd88::1",
+ "cost": 0,
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet manual
+up ip -6 route add fd89::1/128 via fd88::1 dev eth0
+down ip -6 route del fd89::1/128 via fd88::1 dev eth0
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv6_static_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv6",
+ "proto": "static",
+ "address": "fe80::ba27:ebff:fe1c:5477",
+ "mask": 64
+ }
+ ]
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "fd89::1/128",
+ "next": "fd88::1",
+ "cost": 0,
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 static
+address fe80::ba27:ebff:fe1c:5477
+netmask 64
+up ip -6 route add fd89::1/128 via fd88::1 dev eth0
+down ip -6 route del fd89::1/128 via fd88::1 dev eth0
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ipv6_dhcp_route(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "eth0",
+ "type": "ethernet",
+ "addresses": [
+ {
+ "family": "ipv6",
+ "proto": "dhcp"
+ }
+ ]
+ }
+ ],
+ "routes": [
+ {
+ "device": "eth0",
+ "destination": "fd89::1/128",
+ "next": "fd88::1"
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth0
+iface eth0 inet6 dhcp
+up ip -6 route add fd89::1/128 via fd88::1 dev eth0
+down ip -6 route del fd89::1/128 via fd88::1 dev eth0
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_multiple_routes(self):
+ o = Raspbian({
+ "routes": [
+ {
+ "destination": "192.168.4.1/24",
+ "next": "192.168.2.2",
+ "device": "eth1"
+ },
+ {
+ "destination": "fd89::1/128",
+ "next": "fd88::1",
+ "device": "eth1"
+ }
+ ],
+ "interfaces": [
+ {
+ "type": "ethernet",
+ "name": "eth1"
+ }
+ ]
+ })
+
+ expected = """# config: /etc/network/interfaces
+
+auto eth1
+iface eth1 inet manual
+post-up route add -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+pre-up route del -net 192.168.4.1 netmask 255.255.255.0 gw 192.168.2.2
+up ip -6 route add fd89::1/128 via fd88::1 dev eth0
+down ip -6 route del fd89::1/128 via fd88::1 dev eth0
+
+"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_system.py b/tests/raspbian/test_system.py
new file mode 100644
index 000000000..09688daaf
--- /dev/null
+++ b/tests/raspbian/test_system.py
@@ -0,0 +1,50 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestSystem(unittest.TestCase, _TabsMixin):
+
+ def test_general(self):
+ o = Raspbian({
+ "general": {
+ "hostname": "test-system",
+ "timezone": "Europe/Rome"
+ }
+ })
+
+ expected = """# config: /etc/hostname
+
+test-system
+
+# script: /scripts/general.sh
+
+/etc/init.d/hostname.sh start
+echo "Hostname of device has been modified"
+timedatectl set-timezone Europe/Rome
+echo "Timezone has changed to Europe/Rome"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_ntp(self):
+ o = Raspbian({
+ "ntp": {
+ "enabled": True,
+ "enable_server": False,
+ "server": [
+ "0.pool.ntp.org",
+ "1.pool.ntp.org",
+ "2.pool.ntp.org"
+ ]
+ }
+ })
+
+ expected = """# config: /etc/ntp.conf
+
+server 0.pool.ntp.org
+server 1.pool.ntp.org
+server 2.pool.ntp.org
+"""
+ self.assertEqual(o.render(), expected)
diff --git a/tests/raspbian/test_wpasupplicant.py b/tests/raspbian/test_wpasupplicant.py
new file mode 100644
index 000000000..76d83bb18
--- /dev/null
+++ b/tests/raspbian/test_wpasupplicant.py
@@ -0,0 +1,263 @@
+import unittest
+
+from netjsonconfig import Raspbian
+from netjsonconfig.utils import _TabsMixin
+
+
+class TestWpaSupplicant(unittest.TestCase, _TabsMixin):
+
+ def test_wep_open(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "mode": "station",
+ "radio": "radio0",
+ "ssid": "wep-test",
+ "bssid": "01:23:45:67:89:ab",
+ "encryption": {
+ "protocol": "wep_open",
+ "key": "12345"
+ }
+ },
+ }
+ ]
+ })
+
+ expected = """# config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+network={
+ssid="wep-test"
+key_mgmt=NONE
+wep_key0="12345"
+}
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_wep_shared(self):
+ o = Raspbian({
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "mode": "station",
+ "radio": "radio0",
+ "ssid": "wep-test",
+ "bssid": "01:23:45:67:89:ab",
+ "encryption": {
+ "protocol": "wep_shared",
+ "key": "12345"
+ }
+ },
+ }
+ ]
+ })
+
+ expected = """# config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+network={
+ssid="wep-test"
+key_mgmt=NONE
+wep_key0="12345"
+auth_algs=shared
+}
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_wpa2_personal_sta(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ }
+ ],
+ "interfaces": [
+ {
+ "type": "wireless",
+ "name": "wlan0",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "station",
+ "ssid": "Test",
+ "bssid": "00:11:22:33:44:55",
+ "encryption": {
+ "protocol": "wpa2_personal",
+ "key": "changeme",
+ },
+ },
+ }
+ ]
+ })
+
+ expected = """# config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+network={
+ssid="Test"
+key="changeme"
+key_mgmt=WPA-PSK
+}
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)
+
+ def test_wpa2_enterprise_client(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 36,
+ "channel_width": 20,
+ "tx_power": 3
+ }
+ ],
+ "interfaces": [
+ {
+ "name": "wlan0",
+ "type": "wireless",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "station",
+ "ssid": "enterprise-client",
+ "bssid": "00:26:b9:20:5f:09",
+ "encryption": {
+ "protocol": "wpa2_enterprise",
+ "cipher": "auto",
+ "eap_type": "tls",
+ "identity": "test-identity",
+ "password": "test-password",
+ }
+ }
+ }
+ ]
+ })
+
+ expected = """# config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+network={
+ssid="enterprise-client"
+eap=TLS
+identity="test-identity"
+password="test-password"
+}
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+
+ self.assertEqual(o.render(), expected)
+
+ def test_no_encryption(self):
+ o = Raspbian({
+ "radios": [
+ {
+ "name": "radio0",
+ "phy": "phy0",
+ "driver": "mac80211",
+ "protocol": "802.11n",
+ "channel": 3,
+ "channel_width": 20,
+ "tx_power": 3
+ }
+ ],
+ "interfaces": [
+ {
+ "type": "wireless",
+ "name": "wlan0",
+ "wireless": {
+ "radio": "radio0",
+ "mode": "station",
+ "ssid": "ap-ssid-example",
+ "bssid": "00:11:22:33:44:55",
+ },
+ }
+ ]
+ })
+
+ expected = """# config: /etc/wpa_supplicant/wpa_supplicant.conf
+
+network={
+ssid="ap-ssid-example"
+key_mgmt=NONE
+}
+
+# config: /etc/network/interfaces
+
+auto wlan0
+iface wlan0 inet manual
+
+# script: /scripts/ipv4_forwarding.sh
+
+sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
+sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
+sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
+sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
+sudo sh -c "iptables-save > /etc/iptables.ipv4.nat"
+
+"""
+ self.assertEqual(o.render(), expected)