Commit da6a33e3 authored by Stephen Morris's avatar Stephen Morris
Browse files

[trac554] Merge branch 'master' into trac554

Conflicts:
	src/lib/asiolink/dns_service.cc
	src/lib/asiolink/interval_timer.cc
	src/lib/asiolink/io_service.cc
	src/lib/asiolink/recursive_query.cc
	src/lib/asiolink/tcp_server.cc
	src/lib/asiolink/udp_query.cc
	src/lib/asiolink/udp_server.cc
parents 6dad3efd d0181f27
176. [func] zhang likun
src/lib/cache: Rename one interface: from lookupClosestRRset()
to lookupDeepestNS(), and remove one parameter of it.
(Trac #492, git ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae)
175. [bug] jerry
src/bin/xfrout: Xfrout use the case-sensitive mode to compress
names in an AXFR massage.
(Trac #253, git 004e382616150f8a2362e94d3458b59bb2710182)
174. [bug]* jinmei
src/lib/dns: revised dnssectime functions so that they don't rely
on the time_t type (whose size varies on different systems, which
can lead to subtle bugs like some form of "year 2038 problem").
Also handled 32-bit wrap around issues more explicitly, with more
detailed tests. The function API has been changed, but the effect
should be minimal because these functions are mostly private.
(Trac #61, git 09ece8cdd41c0f025e8b897b4883885d88d4ba5d)
173. [bug] jerry
python/isc/notify: A notify_out test fails without network
connectivity, encapsulate the socket behavior using a mock
socket class to fix it.
(Trac #346, git 319debfb957641f311102739a15059f8453c54ce)
172. [func] jelte
Improved the bindctl cli in various ways, mainly concerning
list and map item addressing, the correct display of actual values,
and internal help.
(Trac #384, git e5fb3bc1ed5f3c0aec6eb40a16c63f3d0fc6a7b2)
171. [func] feng, jerry, jinmei, vorner
b10-auth, src/lib/datasrc: in memory data source now works as a
complete data source for authoritative DNS servers and b10-auth
uses it. It still misses major features, however, including
DNSSEC support and zone transfer.
(Last trac #553, but many more,
git 6f031a09a248e7684723c000f3e8cc981dcdb349)
170. [bug] jinmei
Tightened validity checks in the NSEC3 constructors, both "from
"text" and "from wire". Specifically, wire data containing
invalid type bitmaps or invalid lengths of salt or hash is now
correctly rejected.
(Trac #117, git 9c690982f24fef19c747a72f43c4298333a58f48)
169. [func] zhang likun, jelte
Added a basic implementation for a resolver cache (though not
used yet).
(Trac #449, git 8aa3b2246ae095bbe7f855fd11656ae3bdb98986)
168. [bug] vorner
Boss no longer has the -f argument, which was undocumented and
stayed as a relict of previous versions, currently causing only
strange behaviour.
(Trac #572, git 17f237478961005707d649a661cc72a4a0d612d4)
167. [bug] naokikambe
Fixed failure of termination of msgq_test.py with python3
coverage(3.3.1)
(Trac #573, git 0e6a18e12f61cc482e07078776234f32605312e5)
166. [func] jelte
The resolver now sends back a SERVFAIL when there is a client
timeout (timeout_client config setting), but it will not stop
......@@ -16,10 +78,10 @@
(Trac #452, git c9f6acc81e24c4b8f0eb351123dc7b43f64e0914)
163. [func] vorner
The pimpl design pattern is used in UDPServer, with a shared pointer. This
makes it smaller to copy (which is done a lot as a sideeffect of being
coroutine) and speeds applications of this class (notably b10-auth) up by
around 10%.
The pimpl design pattern is used in UDPServer, with a shared
pointer. This makes it smaller to copy (which is done a lot as a
sideeffect of being coroutine) and speeds applications of this
class (notably b10-auth) up by around 10%.
(Trac #537, git 94cb95b1d508541201fc064302ba836164d3cbe6)
162. [func] stephen
......
......@@ -282,3 +282,4 @@ EXTRA_DIST += ext/asio/asio/is_write_buffered.hpp
EXTRA_DIST += ext/asio/asio/buffered_read_stream_fwd.hpp
EXTRA_DIST += ext/asio/asio/socket_acceptor_service.hpp
EXTRA_DIST += ext/asio/asio.hpp
EXTRA_DIST += ext/coroutine/coroutine.h
......@@ -712,6 +712,8 @@ AC_CONFIG_FILES([Makefile
src/lib/testutils/testdata/Makefile
src/lib/nsas/Makefile
src/lib/nsas/tests/Makefile
src/lib/cache/Makefile
src/lib/cache/tests/Makefile
])
AC_OUTPUT([doc/version.ent
src/bin/cfgmgr/b10-cfgmgr.py
......
......@@ -568,7 +568,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils ../src/lib/cache
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
......
......@@ -12,45 +12,44 @@
"item_type": "list",
"item_optional": true,
"item_default": [],
"list_item_spec": {
"item_name": "list_element",
"list_item_spec":
{ "item_name": "list_element",
"item_type": "map",
"item_optional": false,
"item_default": {},
"map_item_spec": [
{ "item_name": "type",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "class",
"item_type": "string",
"item_optional": false,
"item_default": "IN"
},
{ "item_name": "zones",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec": {
"item_name": "list_element",
"item_type": "map",
"item_optional": true,
"map_item_spec": [
{ "item_name": "origin",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "file",
"item_type": "string",
"item_optional": false,
"item_default": ""
}
]
}
}
]
"map_item_spec": [
{ "item_name": "type",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "class",
"item_type": "string",
"item_optional": false,
"item_default": "IN"
},
{ "item_name": "zones",
"item_type": "list",
"item_optional": false,
"item_default": [],
"list_item_spec":
{ "item_name": "list_element",
"item_type": "map",
"item_optional": true,
"item_default": { "origin": "", "file": "" },
"map_item_spec": [
{ "item_name": "origin",
"item_type": "string",
"item_optional": false,
"item_default": ""
},
{ "item_name": "file",
"item_type": "string",
"item_optional": false,
"item_default": ""
}]
}
}]
}
},
{ "item_name": "statistics-interval",
......
......@@ -195,8 +195,7 @@ class BoB:
"""Boss of BIND class."""
def __init__(self, msgq_socket_file=None, dns_port=5300, address=None,
forward=None, nocache=False, verbose=False, setuid=None,
username=None):
nocache=False, verbose=False, setuid=None, username=None):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
......@@ -206,11 +205,6 @@ class BoB:
"""
self.address = address
self.dns_port = dns_port
self.forward = forward
if forward:
self.resolver = True
else:
self.resolver = False
self.cc_session = None
self.ccs = None
self.cfg_start_auth = True
......@@ -422,26 +416,19 @@ class BoB:
"""
Start the Authoritative server
"""
# XXX: this must be read from the configuration manager in the future
if self.resolver:
dns_prog = 'b10-resolver'
else:
dns_prog = 'b10-auth'
dnsargs = [dns_prog]
if not self.resolver:
# The resolver uses configuration manager for these
dnsargs += ['-p', str(self.dns_port)]
if self.address:
dnsargs += ['-a', str(self.address)]
if self.nocache:
dnsargs += ['-n']
authargs = ['b10-auth']
authargs += ['-p', str(self.dns_port)]
if self.address:
authargs += ['-a', str(self.address)]
if self.nocache:
authargs += ['-n']
if self.uid:
dnsargs += ['-u', str(self.uid)]
authargs += ['-u', str(self.uid)]
if self.verbose:
dnsargs += ['-v']
authargs += ['-v']
# ... and start
self.start_process("b10-auth", dnsargs, c_channel_env,
self.start_process("b10-auth", authargs, c_channel_env,
self.dns_port, self.address)
def start_resolver(self, c_channel_env):
......@@ -739,8 +726,6 @@ def check_addr(option, opt_str, value, parser):
try:
if opt_str in ['-a', '--address']:
parser.values.address = isc.net.parse.addr_parse(value)
elif opt_str in ['-f', '--forward']:
parser.values.forward = isc.net.parse.addr_parse(value)
else:
raise OptionValueError("Unknown option " + opt_str)
except ValueError:
......@@ -761,9 +746,6 @@ def main():
parser.add_option("-a", "--address", dest="address", type="string",
action="callback", callback=check_addr, default=None,
help="address the DNS server will use (default: listen on all addresses)")
parser.add_option("-f", "--forward", dest="forward", type="string",
action="callback", callback=check_addr, default=None,
help="nameserver to which DNS queries should be forwarded")
parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
type="string", default=None,
help="UNIX domain socket file the b10-msgq daemon will use")
......@@ -833,8 +815,8 @@ def main():
# Go bob!
boss_of_bind = BoB(options.msgq_socket_file, options.dns_port,
options.address, options.forward, options.nocache,
options.verbose, setuid, username)
options.address, options.nocache, options.verbose,
setuid, username)
startup_result = boss_of_bind.startup()
if startup_result:
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
......
......@@ -51,7 +51,6 @@ except ImportError:
my_readline = sys.stdin.readline
CSV_FILE_NAME = 'default_user.csv'
FAIL_TO_CONNECT_WITH_CMDCTL = "Fail to connect with b10-cmdctl module, is it running?"
CONFIG_MODULE_NAME = 'config'
CONST_BINDCTL_HELP = """
usage: <module name> <command name> [param1 = value1 [, param2 = value2]]
......@@ -92,10 +91,13 @@ class BindCmdInterpreter(Cmd):
Cmd.__init__(self)
self.location = ""
self.prompt_end = '> '
self.prompt = self.prompt_end
if sys.stdin.isatty():
self.prompt = self.prompt_end
else:
self.prompt = ""
self.ruler = '-'
self.modules = OrderedDict()
self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl"))
self.add_module_info(ModuleInfo("help", desc = "Get help for bindctl."))
self.server_port = server_port
self.conn = ValidatedHTTPSConnection(self.server_port,
ca_certs=pem_file)
......@@ -119,8 +121,8 @@ class BindCmdInterpreter(Cmd):
self.cmdloop()
except FailToLogin as err:
print(err)
print(FAIL_TO_CONNECT_WITH_CMDCTL)
# error already printed when this was raised, ignoring
pass
except KeyboardInterrupt:
print('\nExit from bindctl')
......@@ -270,8 +272,10 @@ class BindCmdInterpreter(Cmd):
return line
def postcmd(self, stop, line):
'''Update the prompt after every command'''
self.prompt = self.location + self.prompt_end
'''Update the prompt after every command, but only if we
have a tty as output'''
if sys.stdin.isatty():
self.prompt = self.location + self.prompt_end
return stop
def _prepare_module_commands(self, module_spec):
......@@ -375,7 +379,14 @@ class BindCmdInterpreter(Cmd):
if cmd.command == "help" or ("help" in cmd.params.keys()):
self._handle_help(cmd)
elif cmd.module == CONFIG_MODULE_NAME:
self.apply_config_cmd(cmd)
try:
self.apply_config_cmd(cmd)
except isc.cc.data.DataTypeError as dte:
print("Error: " + str(dte))
except isc.cc.data.DataNotFoundError as dnfe:
print("Error: " + str(dnfe))
except KeyError as ke:
print("Error: missing " + str(ke))
else:
self.apply_cmd(cmd)
......@@ -396,9 +407,24 @@ class BindCmdInterpreter(Cmd):
def do_help(self, name):
print(CONST_BINDCTL_HELP)
for k in self.modules.keys():
print("\t", self.modules[k])
for k in self.modules.values():
n = k.get_name()
if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
print(" %s" % n)
print(textwrap.fill(k.get_desc(),
initial_indent=" ",
subsequent_indent=" " +
" " * CONST_BINDCTL_HELP_INDENT_WIDTH,
width=70))
else:
print(textwrap.fill("%s%s%s" %
(k.get_name(),
" "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
k.get_desc()),
initial_indent=" ",
subsequent_indent=" " +
" " * CONST_BINDCTL_HELP_INDENT_WIDTH,
width=70))
def onecmd(self, line):
if line == 'EOF' or line.lower() == "quit":
......@@ -411,7 +437,19 @@ class BindCmdInterpreter(Cmd):
Cmd.onecmd(self, line)
def remove_prefix(self, list, prefix):
return [(val[len(prefix):]) for val in list]
"""Removes the prefix already entered, and all elements from the
list that don't match it"""
if prefix.startswith('/'):
prefix = prefix[1:]
new_list = []
for val in list:
if val.startswith(prefix):
new_val = val[len(prefix):]
if new_val.startswith("/"):
new_val = new_val[1:]
new_list.append(new_val)
return new_list
def complete(self, text, state):
if 0 == state:
......@@ -502,8 +540,7 @@ class BindCmdInterpreter(Cmd):
self._validate_cmd(cmd)
self._handle_cmd(cmd)
except (IOError, http.client.HTTPException) as err:
print('Error!', err)
print(FAIL_TO_CONNECT_WITH_CMDCTL)
print('Error: ', err)
except BindCtlException as err:
print("Error! ", err)
self._print_correct_usage(err)
......@@ -541,87 +578,115 @@ class BindCmdInterpreter(Cmd):
Raises a KeyError if the command was not complete
'''
identifier = self.location
try:
if 'identifier' in cmd.params:
if not identifier.endswith("/"):
identifier += "/"
if cmd.params['identifier'].startswith("/"):
identifier = cmd.params['identifier']
else:
identifier += cmd.params['identifier']
# Check if the module is known; for unknown modules
# we currently deny setting preferences, as we have
# no way yet to determine if they are ok.
module_name = identifier.split('/')[1]
if self.config_data is None or \
not self.config_data.have_specification(module_name):
print("Error: Module '" + module_name + "' unknown or not running")
return
if 'identifier' in cmd.params:
if not identifier.endswith("/"):
identifier += "/"
if cmd.params['identifier'].startswith("/"):
identifier = cmd.params['identifier']
else:
if cmd.params['identifier'].startswith('['):
identifier = identifier[:-1]
identifier += cmd.params['identifier']
# Check if the module is known; for unknown modules
# we currently deny setting preferences, as we have
# no way yet to determine if they are ok.
module_name = identifier.split('/')[1]
if module_name != "" and (self.config_data is None or \
not self.config_data.have_specification(module_name)):
print("Error: Module '" + module_name + "' unknown or not running")
return
if cmd.command == "show":
values = self.config_data.get_value_maps(identifier)
for value_map in values:
line = value_map['name']
if value_map['type'] in [ 'module', 'map', 'list' ]:
line += "/"
else:
line += ":\t" + json.dumps(value_map['value'])
line += "\t" + value_map['type']
line += "\t"
if value_map['default']:
line += "(default)"
if value_map['modified']:
line += "(modified)"
print(line)
elif cmd.command == "add":
self.config_data.add_value(identifier, cmd.params['value'])
elif cmd.command == "remove":
if 'value' in cmd.params:
self.config_data.remove_value(identifier, cmd.params['value'])
if cmd.command == "show":
# check if we have the 'all' argument
show_all = False
if 'argument' in cmd.params:
if cmd.params['argument'] == 'all':
show_all = True
elif 'identifier' not in cmd.params:
# no 'all', no identifier, assume this is the
#identifier
identifier += cmd.params['argument']
else:
self.config_data.remove_value(identifier, None)
elif cmd.command == "set":
if 'identifier' not in cmd.params:
print("Error: missing identifier or value")
print("Error: unknown argument " + cmd.params['argument'] + ", or multiple identifiers given")
return
values = self.config_data.get_value_maps(identifier, show_all)
for value_map in values:
line = value_map['name']
if value_map['type'] in [ 'module', 'map' ]:
line += "/"
elif value_map['type'] == 'list' \
and value_map['value'] != []:
# do not print content of non-empty lists if
# we have more data to show
line += "/"
else:
parsed_value = None
try:
parsed_value = json.loads(cmd.params['value'])
except Exception as exc:
# ok could be an unquoted string, interpret as such
parsed_value = cmd.params['value']
self.config_data.set_value(identifier, parsed_value)
elif cmd.command == "unset":
self.config_data.unset(identifier)
elif cmd.command == "revert":
self.config_data.clear_local_changes()
elif cmd.command == "commit":
self.config_data.commit()
elif cmd.command == "diff":
print(self.config_data.get_local_changes());
elif cmd.command == "go":
self.go(identifier)
except isc.cc.data.DataTypeError as dte:
print("Error: " + str(dte))
except isc.cc.data.DataNotFoundError as dnfe:
print("Error: " + identifier + " not found")
except KeyError as ke:
print("Error: missing " + str(ke))
raise ke
line += "\t" + json.dumps(value_map['value'])
line += "\t" + value_map['type']
line += "\t"
if value_map['default']:
line += "(default)"
if value_map['modified']:
line += "(modified)"
print(line)
elif cmd.command == "show_json":
if identifier == "":
print("Need at least the module to show the configuration in JSON format")
else:
data, default = self.config_data.get_value(identifier)
print(json.dumps(data))
elif cmd.command == "add":
if 'value' in cmd.params:
self.config_data.add_value(identifier, cmd.params['value'])
else:
self.config_data.add_value(identifier)
elif cmd.command == "remove":
if 'value' in cmd.params:
self.config_data.remove_value(identifier, cmd.params['value'])
else:
self.config_data.remove_value(identifier, None)
elif cmd.command == "set":
if 'identifier' not in cmd.params:
print("Error: missing identifier or value")
else:
parsed_value = None
try:
parsed_value = json.loads(cmd.params['value'])
except Exception as exc:
# ok could be an unquoted string, interpret as such
parsed_value = cmd.params['value']
self.config_data.set_value(identifier, parsed_value)
elif cmd.command == "unset":
self.config_data.unset(identifier)
elif cmd.command == "revert":
self.config_data.clear_local_changes()
elif cmd.command == "commit":
self.config_data.commit()
elif cmd.command == "diff":
print(self.config_data.get_local_changes());
elif cmd.command == "go":
self.go(identifier)
def go(self, identifier):
'''Handles the config go command, change the 'current' location
within the configuration tree'''
# this is just to see if it exists
self.config_data.get_value(identifier)
# some sanitizing
identifier = identifier.replace("//", "/")
if not identifier.startswith("/"):
identifier = "/" + identifier
if identifier.endswith("/"):
identifier = identifier[:-1]
self.location = identifier
within the configuration tree. '..' will be interpreted as
'up one level'.'''
id_parts = isc.cc.data.split_identifier(identifier)
new_location = ""
for id_part in id_parts:
if (id_part == ".."):
# go 'up' one level
new_location, a, b = new_location.rpartition("/")
else:
new_location += "/" + id_part
# check if exists, if not, revert and error
v,d = self.config_data.get_value(new_location)
if v is None:
print("Error: " + identifier + " not found")
return
self.location = new_location
def apply_cmd(self, cmd):
'''Handles a general module command'''
......
......@@ -33,51 +33,60 @@ isc.util.process.rename()
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "bindctl 20101201 (BIND 10 @PACKAGE_VERSION@)"
DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Boss/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
def prepare_config_commands(tool):
'''Prepare fixed commands for local configuration editing'''
module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands")
cmd = CommandInfo(name = "show", desc = "Show configuration")
param = ParamInfo(name = "identifier", type = "string", optional=True)
module = ModuleInfo(name = CONFIG_MODULE_NAME, desc = "Configuration commands.")
cmd = CommandInfo(name = "show", desc = "Show configuration.")