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/backends/raspbian.rst b/docs/source/backends/raspbian.rst new file mode 100644 index 000000000..a93ff87fe --- /dev/null +++ b/docs/source/backends/raspbian.rst @@ -0,0 +1,802 @@ +================ +Raspbian Backend +================ + +.. include:: ../_github.rst + +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, + "tx_power": 3 + }, + ], + "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, + "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" + } + } + } + ] + } + +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 ``OpenWrt`` 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/index.rst b/docs/source/index.rst index 110b4c96b..6b65d36da 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -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..7d79a24be --- /dev/null +++ b/netjsonconfig/backends/raspbian/converters/interfaces.py @@ -0,0 +1,81 @@ +from ipaddress import IPv4Interface, ip_network + +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')) + address['netmask'] = IPv4Interface(address_mask).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(route.get('next')).version == 4: + route['version'] = 4 + destination = IPv4Interface(route['destination']).with_netmask + dest, dest_mask = destination.split('/') + route['dest'] = dest + route['dest_mask'] = dest_mask + del route['destination'] + elif ip_network(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)