Skip to content

Commit

Permalink
Set network configuration by hostname look-up
Browse files Browse the repository at this point in the history
See PR #28
  • Loading branch information
cydanil authored Apr 22, 2020
2 parents 8ce830a + 0fc0e92 commit 5933fa9
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 28 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ Launch it so:
In the main window, you will be given a list of discovered devices. Clicking on one
will show its configuration window.

The most common operation is the setting of the hostname, and a simple window will
pop up.
For further network settings, hit `Advanced`.
The most common operation is the configuration of network setting from the hostname,
and a simple window will pop up, if found in the DNS.
For further configuration, hit `Advanced`.

The gui is further documented in [ipa_gui/gui.md](ipa_gui/gui.md)

Expand Down
34 changes: 25 additions & 9 deletions ipa_gui/configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ipassign import Configuration, acknowledgements

from .networking import gethostbyname, network
from .networking import from_hostname, network

# These are used to validate hostnames
VALID_HN_CHARS = string.ascii_letters + string.digits + '-'
Expand Down Expand Up @@ -50,10 +50,11 @@ def display_config_window(config: Configuration = None) -> None:


class HostnameWindow(QObject):
"""HostnameWindow only allows the setting of a device's hostname.
"""HostnameWindow sets a device's configuration from its hostname, by
performing look-ups.
Setting the hostname is the most common operation, and is ipassign's
default mode of operation.
Configuring network settings by hostname is the most common operation, and
is ipassign's default mode of operation.
This mode of setting will write the configuration to flash and apply it
dynamically.
Expand Down Expand Up @@ -118,7 +119,9 @@ def validator(self):

if (content and all([c in VALID_HN_CHARS for c in content])
and not content.startswith('-')):
color = GREEN
ok, _ = from_hostname(content)
if ok:
color = GREEN

if color is GREEN:
self.pbApply.setEnabled(True)
Expand All @@ -138,11 +141,17 @@ def switch_mode(self):

def apply(self):
hostname = self.leHostname.text()
self.log.emit(f'Renaming {self._config.hostname} -> {hostname}')
self.log.emit(f'Resetting {self._config.hostname} -> {hostname}')
self._config.hostname = hostname
config, self._config = self._config, None
self.parent.close()

# The hostname was already validated prior to enabling pbApply.
_, ret = from_hostname(hostname)
config.ip = ret['ip']
config.gw = ret['gw']
config.bc = ret['bc']
config.nm = ret['nm']
config.flash = True
config.dynamic = True

Expand Down Expand Up @@ -406,9 +415,16 @@ def show(self, config: Configuration) -> None:
self.parent.show()

def query_dns(self):
val = gethostbyname(self.leHostname.text())
if val:
self.leIP.setText(val)
ok, ret = from_hostname(self.leHostname.text())
if ok:
self.leIP.setText(ret['ip'])
self.leGateway.setText(ret['gw'])
self.leNetmask.setText(ret['nm'])
self.leBroadcast.setText(ret['bc'])
else:
# pbApply is purposefully not disabled here.
self.leHostname.setStyleSheet('QLineEdit { background-color: %s }'
% RED)

def reset(self):
self.show(self._config)
Expand Down
11 changes: 7 additions & 4 deletions ipa_gui/gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ properties.

## HostnameWindow

HostnameWindow only allows the setting of a device's hostname.
Setting the hostname is the most common operation, and is ipassign's default
mode of operation.
HostnameWindow sets a device's configuration from its hostname, by performing
look-ups.
Configuring network settings by hostname is the most common operation, and is
passign's default mode of operation.

The hostname is validated with respect to RFC 1123, section 2.1. If the
hostname is not valid, the `Apply` button is disabled.

The hostname will be written to flash and applied dynamically.
If found in DNS, the hostname and its network settings will be written to flash
and applied dynamically.
These settings are found by `networking.from_hostname`.

The `Advanced` push-button allows to switch to the `NetworkWindow` view.

Expand Down
16 changes: 12 additions & 4 deletions ipa_gui/networking.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import socket
import struct

from typing import Union
import netifaces

from PyQt5.QtCore import pyqtSignal, QObject

Expand Down Expand Up @@ -113,8 +113,16 @@ def send_configuration(self, config: Configuration) -> commands:
network = NetworkInterface()


def gethostbyname(name: str) -> Union[bool, str]:
def from_hostname(name: str) -> dict:
try:
return socket.gethostbyname(name)
ip = socket.gethostbyname(name)
except socket.gaierror:
return False
return False, 'Not a known hostname'

gw, iface = netifaces.gateways()['default'][netifaces.AF_INET]

info = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]
nm = info['netmask']
bc = info['broadcast']

return True, {'ip': ip, 'gw': gw, 'nm': nm, 'bc': bc}
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@

setup(
name='icepap-ipassign',
version='2.2.0',
version='2.3.0',
author='Cyril Danilevski',
author_email='[email protected]',
description='A tool to configure IcePAP network settings',
long_description=long_description,
long_description_content_type="text/markdown",
url='https://github.com/cydanil/icepap-ipassign',
packages=['ipassign', 'ipa_gui', 'ipa_utils'],
install_requires=['PyQt5>=5.12.0'],
install_requires=['PyQt5>=5.12.0', 'netifaces>=0.10.9'],
tests_require=['pytest'],
python_requires='>=3.7',
entry_points={
Expand Down
34 changes: 28 additions & 6 deletions tests/test_networking.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,41 @@
import os
import socket
import sys

import netifaces

sys.path.insert(0,
os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))

from ipa_gui.networking import gethostbyname # noqa : import not at top of file
from ipa_gui.networking import from_hostname # noqa : import not at top of file


def test_gethostbyname():
def mock_call(val):
def test_from_hostname():
def mock_socket(val):
if val == 'known':
return '172.24.1.105'
raise socket.gaierror
setattr(socket, 'gethostbyname', mock_socket)

def mock_gateways():
return {'default': {2: ('172.24.154.1', 'eth0')}}
setattr(netifaces, 'gateways', mock_gateways)

def mock_ifaddresses(val):
return {17: [{'addr': '6c:2b:59:e8:5e:51',
'broadcast': 'ff:ff:ff:ff:ff:ff'}],
2: [{'addr': '172.24.155.154',
'netmask': '255.255.254.0',
'broadcast': '172.24.155.255'}]}
setattr(netifaces, 'ifaddresses', mock_ifaddresses)

setattr(socket, 'gethostbyname', mock_call)
ok, val = from_hostname('known')
assert ok
assert val == {'ip': '172.24.1.105',
'gw': '172.24.154.1',
'nm': '255.255.254.0',
'bc': '172.24.155.255'}

assert gethostbyname('known') == '172.24.1.105'
assert not gethostbyname('unknown')
ok, val = from_hostname('unknown')
assert not ok
assert val == 'Not a known hostname'

0 comments on commit 5933fa9

Please sign in to comment.