diff --git a/pylint.rc b/pylint.rc index e5beb0738802ef0841335c045ac21cca85c9509d..d3032e520997d005fa0386b8b2d9c2269fae3bd2 100644 --- a/pylint.rc +++ b/pylint.rc @@ -240,7 +240,7 @@ argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. -#argument-rgx= +argument-rgx=(([a-z_][a-z0-9_]{2,40})|(_[a-z0-9_]*)|(__[a-z][a-z0-9_]+__))$ # Naming style matching correct attribute names. attr-naming-style=snake_case diff --git a/tests/dhcpv4/kea_only/config_backend/cb_cmds.py b/tests/dhcpv4/kea_only/config_backend/cb_cmds.py index e070a9d66464b81e4411edd391fb5defb79a952d..e6a7258719ed5ffaa5efd78ea662ace8fc75daad 100644 --- a/tests/dhcpv4/kea_only/config_backend/cb_cmds.py +++ b/tests/dhcpv4/kea_only/config_backend/cb_cmds.py @@ -5,9 +5,19 @@ import srv_control import misc -def setup_server_for_config_backend_cmds(): +def setup_server_for_config_backend_cmds(**kwargs): misc.test_setup() srv_control.config_srv_subnet('$(EMPTY)', '$(EMPTY)') + + for param, val in kwargs.items(): + param = param.replace('_', '-') + if param in ['echo-client-id', 'match-client-id']: + srv_control.set_conf_parameter_global(param, 'true' if val else 'false') + elif param in ['next-server', 'server-hostname', 'boot-file-name']: + srv_control.set_conf_parameter_global(param, '"%s"' % val) + else: + srv_control.set_conf_parameter_global(param, val) + srv_control.add_hooks('$(SOFTWARE_INSTALL_DIR)/lib/kea/hooks/libdhcp_cb_cmds.so') srv_control.add_hooks('$(SOFTWARE_INSTALL_DIR)/lib/kea/hooks/libdhcp_mysql_cb.so') srv_control.open_control_channel( @@ -52,6 +62,19 @@ def rebind_with_nak_answer(ciaddr): srv_msg.response_check_option_content('Response', '54', None, 'value', '$(SRV4_ADDR)') +def send_decline(requested_addr): + misc.test_procedure() + # srv_msg.client_sets_value('Client', 'chaddr', '00:00:00:00:00:22') + # srv_msg.client_does_include_with_value('client_id', '00010203040122') + srv_msg.client_copy_option('server_id') + srv_msg.client_sets_value('Client', 'ciaddr', '0.0.0.0') + srv_msg.client_does_include_with_value('requested_addr', requested_addr) + srv_msg.client_send_msg('DECLINE') + + misc.pass_criteria() + srv_msg.send_dont_wait_for_message() + + def _compare_subnets(received_subnets, exp_subnet): found = False for sn in received_subnets: @@ -65,19 +88,15 @@ def _compare_subnets(received_subnets, exp_subnet): assert found, 'Cannot find subnet with prefix %s' % exp_subnet['subnet'] -def set_subnet(valid_lifetime=None, renew_timer=None, rebind_timer=None, pool="192.168.50.1-192.168.50.100"): +def set_subnet(**kwargs): # prepare command subnet = { "subnet": "192.168.50.0/24", "interface": "$(SERVER_IFACE)", - "pools": [{"pool": pool}]} + "pools": [{"pool": kwargs['pool'] if 'pool' in kwargs else "192.168.50.1-192.168.50.100"}]} - if valid_lifetime is not None: - subnet['valid-lifetime'] = valid_lifetime - if renew_timer is not None: - subnet['renew-timer'] = renew_timer - if rebind_timer is not None: - subnet['rebind-timer'] = rebind_timer + for param, val in kwargs.items(): + subnet[param.replace('_', '-')] = val cmd = {"command": "remote-subnet4-set", "arguments": {"remote": {"type": "mysql"}, @@ -100,7 +119,7 @@ def set_subnet(valid_lifetime=None, renew_timer=None, rebind_timer=None, pool="1 _compare_subnets(response['arguments']['Dhcp4']['subnet4'], subnet) -def del_subnet(op_kind): +def del_subnet(op_kind='by-prefix'): # prepare command if op_kind == 'by-id': cmd = {"command": "remote-subnet4-del-by-id", @@ -130,9 +149,7 @@ def del_subnet(op_kind): assert sn['subnet'] != "192.168.50.0/24" -def set_network(network_valid_lifetime=None, subnet_valid_lifetime=None, - network_renew_timer=None, subnet_renew_timer=None, - network_rebind_timer=None, subnet_rebind_timer=None): +def set_network(**kwargs): # prepare command network = { "name": "floor13", @@ -141,18 +158,13 @@ def set_network(network_valid_lifetime=None, subnet_valid_lifetime=None, "subnet": "192.168.50.0/24", "pools": [{"pool": "192.168.50.1-192.168.50.100"}]}]} - if network_valid_lifetime: - network['valid-lifetime'] = network_valid_lifetime - if subnet_valid_lifetime: - network['subnet4'][0]['valid-lifetime'] = subnet_valid_lifetime - if network_renew_timer: - network['renew_timer'] = network_renew_timer - if subnet_renew_timer: - network['subnet4'][0]['renew-timer'] = subnet_renew_timer - if network_rebind_timer: - network['rebind_timer'] = network_rebind_timer - if subnet_rebind_timer: - network['subnet4'][0]['rebind_timer'] = subnet_rebind_timer + for param, val in kwargs.items(): + level, param = param.split('_', 1) + param = param.replace('_', '-') + if level == 'network': + network[param] = val + else: + network['subnet4'][0][param] = val cmd = {"command": "remote-network4-set", "arguments": {"remote": {"type": "mysql"}, @@ -186,18 +198,11 @@ def set_network(network_valid_lifetime=None, subnet_valid_lifetime=None, assert found, 'Cannot find shared network with name %s' % network['name'] -def set_global_parameter(valid_lifetime=None, renew_timer=None, rebind_timer=None): +def set_global_parameter(**kwargs): # prepare command parameters = [] - - if valid_lifetime is not None: - parameters.append({'name': 'valid-lifetime', 'value': valid_lifetime}) - if renew_timer is not None: - parameters.append({'name': 'renew-timer', 'value': renew_timer}) - if rebind_timer is not None: - parameters.append({'name': 'rebind-timer', 'value': rebind_timer}) - - assert len(parameters) > 0 + for param, val in kwargs.items(): + parameters.append({'name': param.replace('_', '-'), 'value': val}) # TODO: later should be possible to set list of params in one shot for param in parameters: @@ -224,15 +229,22 @@ def set_global_parameter(valid_lifetime=None, renew_timer=None, rebind_timer=Non assert dhcp4_cfg[param['name']] == param['value'] -def get_address(chaddr=None, exp_yiaddr=None, exp_lease_time=7200, exp_renew_timer=None, exp_rebind_timer=None): +def get_address(chaddr=None, client_id=None, + exp_yiaddr=None, exp_lease_time=7200, exp_renew_timer=None, exp_rebind_timer=None, + exp_client_id=None, + exp_next_server=None, exp_server_hostname=None, exp_boot_file_name=None): + # send DISCOVER misc.test_procedure() srv_msg.client_requests_option('1') - if chaddr: + if chaddr is not None: srv_msg.client_sets_value('Client', 'chaddr', chaddr) + if client_id is not None: + srv_msg.client_does_include_with_value('client_id', client_id) srv_msg.client_send_msg('DISCOVER') + # check OFFER msgs = srv_msg.send_wait_for_message('MUST', None, 'OFFER') - if exp_yiaddr: + if exp_yiaddr is not None: assert exp_yiaddr in msgs[0].yiaddr rcvd_yiaddr = msgs[0].yiaddr srv_msg.response_check_include_option('Response', None, '1') @@ -240,12 +252,25 @@ def get_address(chaddr=None, exp_yiaddr=None, exp_lease_time=7200, exp_renew_tim srv_msg.response_check_option_content('Response', '1', None, 'value', '255.255.255.0') srv_msg.response_check_option_content('Response', '54', None, 'value', '$(SRV4_ADDR)') + if exp_client_id is not None: + if exp_client_id == 'missing': + srv_msg.response_check_include_option('Response', 'NOT ', '61') + else: + srv_msg.response_check_include_option('Response', None, '61') + srv_msg.response_check_option_content('Response', '61', None, 'value', exp_client_id) + if exp_next_server is not None: + srv_msg.response_check_content('Response', None, 'siaddr', exp_next_server) + + # send REQUEST misc.test_procedure() + if client_id is not None: + srv_msg.client_does_include_with_value('client_id', client_id) srv_msg.client_copy_option('server_id') srv_msg.client_does_include_with_value('requested_addr', rcvd_yiaddr) srv_msg.client_requests_option('1') srv_msg.client_send_msg('REQUEST') + # check ACK srv_msg.send_wait_for_message('MUST', None, 'ACK') srv_msg.response_check_content('Response', None, 'yiaddr', rcvd_yiaddr) srv_msg.response_check_include_option('Response', None, '1') @@ -264,5 +289,18 @@ def get_address(chaddr=None, exp_yiaddr=None, exp_lease_time=7200, exp_renew_tim srv_msg.response_check_include_option('Response', missing, '59') if not missing: srv_msg.response_check_option_content('Response', '59', None, 'value', exp_rebind_timer) + if exp_client_id is not None: + if exp_client_id == 'missing': + srv_msg.response_check_include_option('Response', 'NOT ', '61') + else: + srv_msg.response_check_include_option('Response', None, '61') + srv_msg.response_check_option_content('Response', '61', None, 'value', exp_client_id) + + if exp_next_server is not None: + srv_msg.response_check_content('Response', None, 'siaddr', exp_next_server) + if exp_server_hostname is not None: + srv_msg.response_check_content('Response', None, 'sname', exp_server_hostname) + if exp_boot_file_name is not None: + srv_msg.response_check_content('Response', None, 'file', exp_boot_file_name) return rcvd_yiaddr diff --git a/tests/dhcpv4/kea_only/config_backend/test_fixed_fields.py b/tests/dhcpv4/kea_only/config_backend/test_fixed_fields.py new file mode 100644 index 0000000000000000000000000000000000000000..c3d575b88299f9f67433f2bb695e6599540fa932 --- /dev/null +++ b/tests/dhcpv4/kea_only/config_backend/test_fixed_fields.py @@ -0,0 +1,82 @@ +"""Kea config backend testing: fixed fields set based on next-server, server-hostname and boot-file-name settings.""" + +import pytest + +from .cb_cmds import setup_server_for_config_backend_cmds +from .cb_cmds import get_address, set_subnet, set_network, set_global_parameter, del_subnet + + +pytestmark = [pytest.mark.kea_only, + pytest.mark.controlchannel, + pytest.mark.hook, + pytest.mark.config_backend] + + +@pytest.mark.v4 +@pytest.mark.parametrize("initial_next_server,initial_server_hostname,initial_boot_file_name", + [(None, None, None), # pick defaults + ('1.1.1.1', 'aaa.example.com', '/boot/aaa')]) # some specific initial values +def test_subnet(initial_next_server, initial_server_hostname, initial_boot_file_name): + setup_server_for_config_backend_cmds(next_server=initial_next_server, + server_hostname=initial_server_hostname, + boot_file_name=initial_boot_file_name) + + # TODO: it does not work yet + set_subnet() + get_address(exp_next_server=initial_next_server if initial_next_server else '0.0.0.0', + exp_server_hostname=initial_server_hostname if initial_server_hostname else '', + exp_boot_file_name=initial_boot_file_name if initial_boot_file_name else '') + + set_global_parameter(next_server='2.2.2.2', + server_hostname='bbb.example.com', + boot_file_name='/boot/bbb') + get_address(exp_next_server='2.2.2.2', + exp_server_hostname='bbb.example.com', + exp_boot_file_name='/boot/bbb') + + del_subnet() + set_subnet(next_server='3.3.3.3', + server_hostname='ccc.example.com', + boot_file_name='/boot/ccc') + get_address(exp_next_server='3.3.3.3', + exp_server_hostname='ccc.example.com', + exp_boot_file_name='/boot/ccc') + + set_subnet(next_server='4.4.4.4', + server_hostname='ddd.example.com', + boot_file_name='/boot/ddd') + get_address(exp_next_server='4.4.4.4', + exp_server_hostname='ddd.example.com', + exp_boot_file_name='/boot/ddd') + + +@pytest.mark.v4 +@pytest.mark.parametrize("initial_next_server,initial_server_hostname,initial_boot_file_name", + [(None, None, None), # pick defaults + ('1.1.1.1', 'aaa.example.com', '/boot/aaa')]) # some specific initial values +def test_network(initial_next_server, initial_server_hostname, initial_boot_file_name): + setup_server_for_config_backend_cmds(next_server=initial_next_server, + server_hostname=initial_server_hostname, + boot_file_name=initial_boot_file_name) + + # TODO: it does not work yet + set_subnet() + get_address(exp_next_server=initial_next_server if initial_next_server else '0.0.0.0', + exp_server_hostname=initial_server_hostname if initial_server_hostname else '', + exp_boot_file_name=initial_boot_file_name if initial_boot_file_name else '') + + set_global_parameter(next_server='2.2.2.2', + server_hostname='bbb.example.com', + boot_file_name='/boot/bbb') + get_address(exp_next_server='2.2.2.2', + exp_server_hostname='bbb.example.com', + exp_boot_file_name='/boot/bbb') + + set_network(network_next_server='3.3.3.3', + network_server_hostname='ccc.example.com', + network_boot_file_name='/boot/ccc') + get_address(exp_next_server='3.3.3.3', + exp_server_hostname='ccc.example.com', + exp_boot_file_name='/boot/ccc') + + # TODO: add more cases but it does not work yet diff --git a/tests/dhcpv4/kea_only/config_backend/test_globals.py b/tests/dhcpv4/kea_only/config_backend/test_globals.py new file mode 100644 index 0000000000000000000000000000000000000000..724744dd35819a3d802073a9d7b56f0951bf6515 --- /dev/null +++ b/tests/dhcpv4/kea_only/config_backend/test_globals.py @@ -0,0 +1,121 @@ +"""Kea config backend testing subnets. TODO """ + +import time + +import pytest + +from .cb_cmds import setup_server_for_config_backend_cmds +from .cb_cmds import send_discovery_with_no_answer, send_decline +from .cb_cmds import get_address, set_subnet, del_subnet, set_global_parameter + + +pytestmark = [pytest.mark.kea_only, + pytest.mark.controlchannel, + pytest.mark.hook, + pytest.mark.config_backend] + + +@pytest.mark.v4 +@pytest.mark.kea_only +@pytest.mark.parametrize("initial_echo_client_id", [None, True, False]) +def test_echo_client_id(initial_echo_client_id): + # Set initial value of echo-client-id in config file and then change it + # using cb-cmds. Observe if client-id is included in responses according to settings. + + # Different initial settings for echo-client-id: default (=True), True and False. + setup_server_for_config_backend_cmds(echo_client_id=initial_echo_client_id) + + set_subnet() + + # Request address and check if client-id is returned according to initial setting. + get_address(client_id='00010203040506', + exp_client_id='00010203040506' if initial_echo_client_id in [None, True] else 'missing') + + # Change setting to NOT return client-id. It should be missing in responses. + set_global_parameter(echo_client_id=False) + get_address(client_id='10010203040506', exp_client_id='missing') + + # Change again setting to return client-id. It should be missing in responses. + set_global_parameter(echo_client_id=True) + get_address(client_id='20010203040506', exp_client_id='20010203040506') + + # Change setting to NOT return client-id. It should be missing in responses. + set_global_parameter(echo_client_id=False) + get_address(client_id='30010203040506', exp_client_id='missing') + + +@pytest.mark.v4 +@pytest.mark.kea_only +@pytest.mark.parametrize("initial_decline_probation_period", [None, 1, 1000]) +def test_decline_and_probation_period(initial_decline_probation_period): + # Set initial value of decline-probation-period in config file and then change it + # using cb-cmds. Observe if the setting is honored in case of sending DECLINE messages. + + # Different initial settings for decline-probation-period: default (=24h), 1 second and 1000 seconds. + setup_server_for_config_backend_cmds(decline_probation_period=initial_decline_probation_period) + + # Prepare subnet with only 1 IP address in a pool. This way when the second DISCOVER is send + # no response should be expected from server. + set_subnet(pool='192.168.50.1-192.168.50.1') + + # Get address and decline it. + addr1 = get_address(exp_yiaddr='192.168.50.1') + send_decline(addr1) + + # Wait a moment. + time.sleep(2) + + # If initial decline-probation-period was 1 second then it should + # be possible to acquire the same IP again, ie. after 1 second it should have been + # returned to pool from probation space. + if initial_decline_probation_period == 1: + addr2 = get_address() + assert addr2 == addr1 + else: + # If initial value was other than 1 second then server should still keep + # the IP in probation and no response should be sent by server. + send_discovery_with_no_answer() + + # Delete subnet. This will delete IP in probation. Ie. start from scratch. + del_subnet() + # Change decline-probation-period from initial to 1000 seconds. + set_global_parameter(decline_probation_period=1000) + # Create new subnet with different pool but still with 1 IP address. + set_subnet(pool='192.168.50.2-192.168.50.2') + + # Now after decline and sleeping 2 seconds the declined address still should + # be in probation and server should not send any response for discover. + addr = get_address(exp_yiaddr='192.168.50.2') + send_decline(addr) + time.sleep(2) + send_discovery_with_no_answer() + + # Start from scratch again. New pool with 1 IP address. + # Probation period is changed now to 1 second. + del_subnet() + set_global_parameter(decline_probation_period=1) + set_subnet(pool='192.168.50.3-192.168.50.3') + + # This time after decline and sleeping the address should be available + # for the following request. + addr1 = get_address(exp_yiaddr='192.168.50.3') + send_decline(addr1) + time.sleep(2) + addr2 = get_address() + assert addr2 == addr1 + + +@pytest.mark.v4 +@pytest.mark.kea_only +@pytest.mark.parametrize("initial_match_client_id", [None, True, False]) +def test_match_client_id(initial_match_client_id): + setup_server_for_config_backend_cmds(match_client_id=initial_match_client_id) + # TODO: complete the test + + +# TODO +# @pytest.mark.v4 +# @pytest.mark.kea_only +# @pytest.mark.parametrize("initial_dhcp4o6_port", [None, True, False]) +# def test_dhcp4o6_port(initial_echo_client_id): +# pass diff --git a/tests/dhcpv4/kea_only/config_backend/test_subnets.py b/tests/dhcpv4/kea_only/config_backend/test_subnets.py index 915d5ca2fbddb8ef5031235afa9f0df29dc5948b..eabe10c8fb7b4922f4cae202e72f2fc8a4e5d785 100644 --- a/tests/dhcpv4/kea_only/config_backend/test_subnets.py +++ b/tests/dhcpv4/kea_only/config_backend/test_subnets.py @@ -67,7 +67,7 @@ def test_subnet_set_and_del_and_set(del_cmd): # define one subnet and now response for discover should be received set_subnet(pool="192.168.50.100-192.168.50.100") - # send discover and now offer should be received + # check getting address get_address(exp_yiaddr='192.168.50.100') # delete added subnet by id or prefix, now there should be no answer to discover @@ -79,7 +79,23 @@ def test_subnet_set_and_del_and_set(del_cmd): # define similar subnet and now response for discover should be received set_subnet(pool="192.168.50.200-192.168.50.200") - # send discover and now offer should be received + # check getting address + get_address(exp_yiaddr='192.168.50.200') + + +@pytest.mark.v4 +def test_subnet_modifications(): + setup_server_for_config_backend_cmds() + + # send discover but no response should come back as there is no subnet defined yet + send_discovery_with_no_answer() + + # define one subnet and check getting address + set_subnet(pool="192.168.50.100-192.168.50.100") + get_address(exp_yiaddr='192.168.50.100') + + # change the pool of the subnet and check getting address + set_subnet(pool="192.168.50.200-192.168.50.200") get_address(exp_yiaddr='192.168.50.200') diff --git a/tests/dhcpv4/kea_only/config_backend/test_timers.py b/tests/dhcpv4/kea_only/config_backend/test_timers.py index 0fd489f9ef652033d3598b5e8623459f73a6ec71..4a34773f2a230eb5afcc946e915bdc27edd2bfed 100644 --- a/tests/dhcpv4/kea_only/config_backend/test_timers.py +++ b/tests/dhcpv4/kea_only/config_backend/test_timers.py @@ -381,3 +381,6 @@ def test_shared_networks_and_valid_lifetime(): time.sleep(2) # now rebinding after lifetime should fail rebind_with_nak_answer(yiaddr6) + + +# TODO: calculate_tee_times, t1-percent, t2-percent diff --git a/tests/dhcpv4/kea_only/test_server_conf.py b/tests/dhcpv4/kea_only/test_server_conf.py index 5c6c215c5c887afb7e25374565cfeb9f509ef6ef..9264c0009f3c359f401e9d736bea0d66ab4ca662 100644 --- a/tests/dhcpv4/kea_only/test_server_conf.py +++ b/tests/dhcpv4/kea_only/test_server_conf.py @@ -11,7 +11,7 @@ import misc @pytest.mark.v4 @pytest.mark.kea_only -def test_v4_echo_client_id(): +def test_v4_echo_client_id_disabled(): misc.test_setup() srv_control.config_srv_subnet('192.168.50.0/24', '192.168.50.1-192.168.50.1') srv_control.set_conf_parameter_global('echo-client-id', 'false') @@ -40,3 +40,38 @@ def test_v4_echo_client_id(): srv_msg.response_check_content('Response', None, 'yiaddr', '192.168.50.1') srv_msg.response_check_include_option('Response', None, '1') srv_msg.response_check_include_option('Response', 'NOT ', '61') + + +@pytest.mark.v4 +@pytest.mark.kea_only +def test_v4_echo_client_id_enabled(): + misc.test_setup() + srv_control.config_srv_subnet('192.168.50.0/24', '192.168.50.1-192.168.50.1') + srv_control.set_conf_parameter_global('echo-client-id', 'true') + srv_control.build_and_send_config_files('SSH', 'config-file') + srv_control.start_srv('DHCP', 'started') + + misc.test_procedure() + srv_msg.client_requests_option('1') + srv_msg.client_does_include_with_value('client_id', '00010203040506') + srv_msg.client_send_msg('DISCOVER') + + misc.pass_criteria() + srv_msg.send_wait_for_message('MUST', None, 'OFFER') + srv_msg.response_check_include_option('Response', None, '1') + srv_msg.response_check_include_option('Response', None, '61') + srv_msg.response_check_option_content('Response', '61', None, 'value', '00010203040506') + + misc.test_procedure() + srv_msg.client_does_include_with_value('client_id', '00010203040506') + srv_msg.client_copy_option('server_id') + srv_msg.client_does_include_with_value('requested_addr', '192.168.50.1') + srv_msg.client_requests_option('1') + srv_msg.client_send_msg('REQUEST') + + misc.pass_criteria() + srv_msg.send_wait_for_message('MUST', None, 'ACK') + srv_msg.response_check_content('Response', None, 'yiaddr', '192.168.50.1') + srv_msg.response_check_include_option('Response', None, '1') + srv_msg.response_check_include_option('Response', None, '61') + srv_msg.response_check_option_content('Response', '61', None, 'value', '00010203040506') diff --git a/tests/softwaresupport/kea4_server/functions.py b/tests/softwaresupport/kea4_server/functions.py index 000780e0e0420c5417c609047818e6ca7956ae7f..b14796f5e4b2bc39f4376502568a05ebc2ff3b0e 100644 --- a/tests/softwaresupport/kea4_server/functions.py +++ b/tests/softwaresupport/kea4_server/functions.py @@ -274,7 +274,7 @@ def disable_client_echo(): world.cfg["simple_options"] = '' else: world.cfg["simple_options"] += ',' - world.cfg["simple_options"] += '"echo-client-id": "False"'.format(**locals()) + world.cfg["simple_options"] += '"echo-client-id": "False"' def host_reservation(reservation_type, reserved_value, unique_host_value_type, unique_host_value, subnet): diff --git a/tests/terrain.py b/tests/terrain.py index 2d2c16b8b35956be69a7cdc609334d611d432b37..dbf4c2bb4c36aebc9511d921c1d9671df010eb32 100644 --- a/tests/terrain.py +++ b/tests/terrain.py @@ -329,7 +329,7 @@ def initialize(scenario): world.hooks = [] world.classification = [] world.reservation_backend = "" - dir_name = str(scenario.name).replace(".", "_").replace('[', '_').replace(']', '_') + dir_name = str(scenario.name).replace(".", "_").replace('[', '_').replace(']', '_').replace('/', '_') world.cfg["dir_name"] = 'tests_results/' + dir_name world.f_cfg.dir_name = world.cfg["dir_name"] world.cfg["subnet"] = ""