diff --git a/plinth/modules/networks/forms.py b/plinth/modules/networks/forms.py index 10101e717..5629c533b 100644 --- a/plinth/modules/networks/forms.py +++ b/plinth/modules/networks/forms.py @@ -59,6 +59,8 @@ available over this interfaces. Select Internal only for trusted networks.'), choices=[('external', 'External'), ('internal', 'Internal')]) ipv4_method = forms.ChoiceField( label=_('IPv4 Addressing Method'), + help_text='"Shared" will start a DHCP Server, "Automatic" will \ + start a DHCP client.', choices=[('auto', 'Automatic (DHCP)'), ('shared', 'Shared'), ('manual', 'Manual')]) @@ -66,6 +68,31 @@ available over this interfaces. Select Internal only for trusted networks.'), label=_('Address'), validators=[validators.validate_ipv4_address], required=False) + ipv4_netmask = forms.CharField( + label=_('Netmask'), + help_text='Optional value. If not given the default netmask \ + (associated with the address) will be used', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_gateway = forms.CharField( + label=_('Gateway'), + help_text='Optional value.', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_dns = forms.CharField( + label=_('DNS Server'), + help_text='Optional value. If this value is given and IPv4 \ + Addressing Method is DHCP, the DNS Servers from DHCP Server \ + will be ignored.', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_second_dns = forms.CharField( + label=_('Second DNS Server'), + help_text='Optional value. If this value is given and IPv4 \ + Addressing Method is DHCP, the DNS Servers from DHCP Server \ + will be ignored.', + validators=[validators.validate_ipv4_address], + required=False) def __init__(self, *args, **kwargs): """Initialize the form, populate interface choices.""" @@ -143,6 +170,27 @@ Point.')) label=_('Address'), validators=[validators.validate_ipv4_address], required=False) + ipv4_netmask = forms.CharField( + label=_('Netmask'), + help_text='Optional value. If not given the default netmask \ + (associated with the address) will be used', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_gateway = forms.CharField( + label=_('Gateway'), + help_text='Optional value.', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_dns = forms.CharField( + label=_('DNS Server'), + help_text='Optional value.', + validators=[validators.validate_ipv4_address], + required=False) + ipv4_second_dns = forms.CharField( + label=_('Second DNS Server'), + help_text='Optional value.', + validators=[validators.validate_ipv4_address], + required=False) def __init__(self, *args, **kwargs): """Initialize the form, populate interface choices.""" diff --git a/plinth/modules/networks/networks.py b/plinth/modules/networks/networks.py index df54438fb..3ad278083 100644 --- a/plinth/modules/networks/networks.py +++ b/plinth/modules/networks/networks.py @@ -140,11 +140,16 @@ def edit(request, uuid): else: ipv4_method = form.cleaned_data['ipv4_method'] ipv4_address = form.cleaned_data['ipv4_address'] + ipv4_netmask = form.cleaned_data['ipv4_netmask'] + ipv4_gateway = form.cleaned_data['ipv4_gateway'] + ipv4_dns = form.cleaned_data['ipv4_dns'] + ipv4_second_dns = form.cleaned_data['ipv4_second_dns'] if connection.get_connection_type() == '802-3-ethernet': network.edit_ethernet_connection( connection, name, interface, zone, ipv4_method, - ipv4_address) + ipv4_address, ipv4_netmask, ipv4_gateway, + ipv4_dns, ipv4_second_dns) elif connection.get_connection_type() == '802-11-wireless': ssid = form.cleaned_data['ssid'] mode = form.cleaned_data['mode'] @@ -153,7 +158,8 @@ def edit(request, uuid): network.edit_wifi_connection( connection, name, interface, zone, ssid, mode, auth_mode, - passphrase, ipv4_method, ipv4_address) + passphrase, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) elif connection.get_connection_type() == 'pppoe': network.edit_pppoe_connection( connection, name, interface, zone, username, password) @@ -176,8 +182,20 @@ def edit(request, uuid): settings_ipv4 = connection.get_setting_ip4_config() form_data['ipv4_method'] = settings_ipv4.get_method() address = network.get_first_ip_address_from_connection(connection) + netmask = network.get_first_netmask_from_connection(connection) + gateway = settings_ipv4.get_gateway() + dns = settings_ipv4.get_dns(0) + second_dns = settings_ipv4.get_dns(1) if address: form_data['ipv4_address'] = address + if netmask: + form_data['ipv4_netmask'] = netmask + if gateway: + form_data['ipv4_gateway'] = gateway + if dns: + form_data['ipv4_dns'] = dns + if second_dns: + form_data['ipv4_second_dns'] = second_dns if settings_connection.get_connection_type() == '802-11-wireless': settings_wireless = connection.get_setting_wireless() @@ -291,9 +309,14 @@ def add_ethernet(request): zone = form.cleaned_data['zone'] ipv4_method = form.cleaned_data['ipv4_method'] ipv4_address = form.cleaned_data['ipv4_address'] + ipv4_netmask = form.cleaned_data['ipv4_netmask'] + ipv4_gateway = form.cleaned_data['ipv4_gateway'] + ipv4_dns = form.cleaned_data['ipv4_dns'] + ipv4_second_dns = form.cleaned_data['ipv4_second_dns'] network.add_ethernet_connection( - name, interface, zone, ipv4_method, ipv4_address) + name, interface, zone, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) return redirect(reverse_lazy('networks:index')) else: form = AddEthernetForm() @@ -356,10 +379,15 @@ def add_wifi(request, ssid=None, interface_name=None): passphrase = form.cleaned_data['passphrase'] ipv4_method = form.cleaned_data['ipv4_method'] ipv4_address = form.cleaned_data['ipv4_address'] + ipv4_netmask = form.cleaned_data['ipv4_netmask'] + ipv4_gateway = form.cleaned_data['ipv4_gateway'] + ipv4_dns = form.cleaned_data['ipv4_dns'] + ipv4_second_dns = form.cleaned_data['ipv4_second_dns'] network.add_wifi_connection( name, interface, zone, ssid, mode, auth_mode, passphrase, - ipv4_method, ipv4_address) + ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) return redirect(reverse_lazy('networks:index')) else: if form_data: diff --git a/plinth/modules/networks/templates/connections_create.html b/plinth/modules/networks/templates/connections_create.html index 319374502..759fba017 100644 --- a/plinth/modules/networks/templates/connections_create.html +++ b/plinth/modules/networks/templates/connections_create.html @@ -45,18 +45,26 @@ if ($("#id_ipv4_method").prop("value") != "manual") { $("#id_ipv4_address").prop("readOnly", true); + $("#id_ipv4_netmask").prop("readOnly", true); + $("#id_ipv4_gateway").prop("readOnly", true); } $("#id_name").focus(); $("#id_ipv4_method").change(function() { - if ($("#id_ipv4_method").prop("value") == 'manual') { - $("#id_ipv4_address").prop("readOnly", false); - $("#id_ipv4_address").prop("required", true); - } else { - $("#id_ipv4_address").val(""); - $("#id_ipv4_address").prop("readOnly", true); - $("#id_ipv4_address").prop("required", false); - } + if ($("#id_ipv4_method").prop("value") == "manual") { + $("#id_ipv4_address").prop("readOnly", false); + $("#id_ipv4_address").prop("required", true); + $("#id_ipv4_netmask").prop("readOnly", false); + $("#id_ipv4_gateway").prop("readOnly", false); + } else { + $("#id_ipv4_address").val(""); + $("#id_ipv4_address").prop("readOnly", true); + $("#id_ipv4_address").prop("required", false); + $("#id_ipv4_netmask").val(""); + $("#id_ipv4_netmask").prop("readOnly", true); + $("#id_ipv4_gateway").val(""); + $("#id_ipv4_gateway").prop("readOnly", true); + } }); $('#id_show_password').change(function() { diff --git a/plinth/modules/networks/templates/connections_edit.html b/plinth/modules/networks/templates/connections_edit.html index 141d1bd7d..88cfadc17 100644 --- a/plinth/modules/networks/templates/connections_edit.html +++ b/plinth/modules/networks/templates/connections_edit.html @@ -45,6 +45,8 @@ if ($("#id_ipv4_method").prop("value") != "manual") { $("#id_ipv4_address").prop("readOnly", true); + $("#id_ipv4_netmask").prop("readOnly", true); + $("#id_ipv4_gateway").prop("readOnly", true); } $("#id_name").focus(); @@ -52,10 +54,16 @@ if ($("#id_ipv4_method").prop("value") == "manual") { $("#id_ipv4_address").prop("readOnly", false); $("#id_ipv4_address").prop("required", true); + $("#id_ipv4_netmask").prop("readOnly", false); + $("#id_ipv4_gateway").prop("readOnly", false); } else { $("#id_ipv4_address").val(""); $("#id_ipv4_address").prop("readOnly", true); $("#id_ipv4_address").prop("required", false); + $("#id_ipv4_netmask").val(""); + $("#id_ipv4_netmask").prop("readOnly", true); + $("#id_ipv4_gateway").val(""); + $("#id_ipv4_gateway").prop("readOnly", true); } }); diff --git a/plinth/network.py b/plinth/network.py index 68979b10a..a046d14ad 100644 --- a/plinth/network.py +++ b/plinth/network.py @@ -201,6 +201,27 @@ def get_first_ip_address_from_connection(connection): return output.strip().split(', ')[0].split('/')[0] +def get_first_netmask_from_connection(connection): + """Return the first IP address of a connection setting. + + XXX: Work around a bug in NetworkManager/Python GI. Remove after + the bug if fixed. + https://bugzilla.gnome.org/show_bug.cgi?id=756380. + """ + command = ['nmcli', '--terse', '--mode', 'tabular', '--fields', + 'ipv4.addresses', 'connection', 'show', connection.get_uuid()] + + output = subprocess.check_output(command).decode() + if '/' not in output: + return None + + CIDR = output.strip().split(', ')[0].split('/')[1] + netmask = socket.inet_ntoa(struct.pack(">I", (0xffffffff << + (32 - int(CIDR))) & + 0xffffffff)) + return netmask + + def get_connection_list(): """Get a list of active and available connections.""" active_uuids = [] @@ -291,7 +312,8 @@ def _update_common_settings(connection, connection_uuid, name, type_, return connection -def _update_ipv4_settings(connection, ipv4_method, ipv4_address): +def _update_ipv4_settings(connection, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns): """Edit IPv4 settings for network manager connections.""" settings = nm.SettingIP4Config.new() connection.add_setting(settings) @@ -299,22 +321,45 @@ def _update_ipv4_settings(connection, ipv4_method, ipv4_address): settings.set_property(nm.SETTING_IP_CONFIG_METHOD, ipv4_method) if ipv4_method == nm.SETTING_IP4_CONFIG_METHOD_MANUAL and ipv4_address: ipv4_address_int = ipv4_string_to_int(ipv4_address) - ipv4_prefix = nm.utils_ip4_get_default_prefix(ipv4_address_int) + if not ipv4_netmask: + ipv4_netmask_int = nm.utils_ip4_get_default_prefix( + ipv4_address_int) - address = nm.IPAddress.new(socket.AF_INET, ipv4_address, ipv4_prefix) + ipv4_netmask_int = nm.utils_ip4_netmask_to_prefix( + ipv4_string_to_int(ipv4_netmask)) + address = nm.IPAddress.new(socket.AF_INET, ipv4_address, + ipv4_netmask_int) settings.add_address(address) - settings.set_property(nm.SETTING_IP_CONFIG_GATEWAY, '0.0.0.0') + if ipv4_dns: + settings.add_dns(ipv4_dns) + if ipv4_second_dns: + settings.add_dns(ipv4_second_dns) + + if not ipv4_gateway: + settings.set_property(nm.SETTING_IP_CONFIG_GATEWAY, '0.0.0.0') + else: + settings.set_property(nm.SETTING_IP_CONFIG_GATEWAY, ipv4_gateway) + else: + if ipv4_dns or ipv4_second_dns: + settings.set_property(nm.SETTING_IP_CONFIG_IGNORE_AUTO_DNS, True) + + if ipv4_dns: + settings.add_dns(ipv4_dns) + if ipv4_second_dns: + settings.add_dns(ipv4_second_dns) def _update_ethernet_settings(connection, connection_uuid, name, interface, - zone, ipv4_method, ipv4_address): + zone, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns): """Create/edit ethernet settings for network manager connections.""" type_ = '802-3-ethernet' connection = _update_common_settings(connection, connection_uuid, name, type_, interface, zone) - _update_ipv4_settings(connection, ipv4_method, ipv4_address) + _update_ipv4_settings(connection, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) # Ethernet settings = connection.get_setting_wired() @@ -377,7 +422,9 @@ def edit_pppoe_connection(connection, name, interface, zone, username, connection.commit_changes(True) -def add_ethernet_connection(name, interface, zone, ipv4_method, ipv4_address): +def add_ethernet_connection(name, interface, zone, ipv4_method, ipv4_address, + ipv4_netmask, ipv4_gateway, ipv4_dns, + ipv4_second_dns): """Add an automatic ethernet connection in network manager. Return the UUID for the connection. @@ -385,31 +432,34 @@ def add_ethernet_connection(name, interface, zone, ipv4_method, ipv4_address): connection_uuid = str(uuid.uuid4()) connection = _update_ethernet_settings( None, connection_uuid, name, interface, zone, ipv4_method, - ipv4_address) + ipv4_address, ipv4_netmask, ipv4_gateway, ipv4_dns, ipv4_second_dns) client = nm.Client.new(None) client.add_connection_async(connection, True, None, _callback, None) return connection_uuid def edit_ethernet_connection(connection, name, interface, zone, ipv4_method, - ipv4_address): + ipv4_address, ipv4_netmask, ipv4_gateway, + ipv4_dns, ipv4_second_dns): """Edit an existing ethernet connection in network manager.""" _update_ethernet_settings( connection, connection.get_uuid(), name, interface, zone, ipv4_method, - ipv4_address) + ipv4_address, ipv4_netmask, ipv4_gateway, ipv4_dns, ipv4_second_dns) connection.commit_changes(True) def _update_wifi_settings(connection, connection_uuid, name, interface, zone, ssid, mode, auth_mode, passphrase, ipv4_method, - ipv4_address): + ipv4_address, ipv4_netmask, ipv4_gateway, ipv4_dns, + ipv4_second_dns): """Create/edit wifi settings for network manager connections.""" type_ = '802-11-wireless' key_mgmt = 'wpa-psk' connection = _update_common_settings(connection, connection_uuid, name, type_, interface, zone) - _update_ipv4_settings(connection, ipv4_method, ipv4_address) + _update_ipv4_settings(connection, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) # Wireless settings = connection.get_setting_wireless() @@ -437,7 +487,8 @@ def _update_wifi_settings(connection, connection_uuid, name, interface, zone, def add_wifi_connection(name, interface, zone, ssid, mode, auth_mode, - passphrase, ipv4_method, ipv4_address): + passphrase, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns): """Add an automatic Wi-Fi connection in network manager. Return the UUID for the connection. @@ -445,18 +496,22 @@ def add_wifi_connection(name, interface, zone, ssid, mode, auth_mode, connection_uuid = str(uuid.uuid4()) connection = _update_wifi_settings( None, connection_uuid, name, interface, zone, ssid, mode, auth_mode, - passphrase, ipv4_method, ipv4_address) + passphrase, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) client = nm.Client.new(None) client.add_connection_async(connection, True, None, _callback, None) return connection_uuid def edit_wifi_connection(connection, name, interface, zone, ssid, mode, - auth_mode, passphrase, ipv4_method, ipv4_address): + auth_mode, passphrase, ipv4_method, ipv4_address, + ipv4_netmask, ipv4_gateway, ipv4_dns, + ipv4_second_dns): """Edit an existing wifi connection in network manager.""" _update_wifi_settings( connection, connection.get_uuid(), name, interface, zone, ssid, mode, - auth_mode, passphrase, ipv4_method, ipv4_address) + auth_mode, passphrase, ipv4_method, ipv4_address, ipv4_netmask, + ipv4_gateway, ipv4_dns, ipv4_second_dns) connection.commit_changes(True)