bind10_src.py.in 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190
  1. #!@PYTHON@
  2. # Copyright (C) 2010,2011 Internet Systems Consortium.
  3. #
  4. # Permission to use, copy, modify, and distribute this software for any
  5. # purpose with or without fee is hereby granted, provided that the above
  6. # copyright notice and this permission notice appear in all copies.
  7. #
  8. # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
  9. # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
  10. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
  11. # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
  12. # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
  13. # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  14. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
  15. # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """
  17. This file implements the Boss of Bind (BoB, or bob) program.
  18. Its purpose is to start up the BIND 10 system, and then manage the
  19. processes, by starting and stopping processes, plus restarting
  20. processes that exit.
  21. To start the system, it first runs the c-channel program (msgq), then
  22. connects to that. It then runs the configuration manager, and reads
  23. its own configuration. Then it proceeds to starting other modules.
  24. The Python subprocess module is used for starting processes, but
  25. because this is not efficient for managing groups of processes,
  26. SIGCHLD signals are caught and processed using the signal module.
  27. Most of the logic is contained in the BoB class. However, since Python
  28. requires that signal processing happen in the main thread, we do
  29. signal handling outside of that class, in the code running for
  30. __main__.
  31. """
  32. import sys; sys.path.append ('@@PYTHONPATH@@')
  33. import os
  34. # If B10_FROM_SOURCE is set in the environment, we use data files
  35. # from a directory relative to that, otherwise we use the ones
  36. # installed on the system
  37. if "B10_FROM_SOURCE" in os.environ:
  38. SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
  39. ADD_LIBEXEC_PATH = False
  40. else:
  41. PREFIX = "@prefix@"
  42. DATAROOTDIR = "@datarootdir@"
  43. SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
  44. ADD_LIBEXEC_PATH = True
  45. import subprocess
  46. import signal
  47. import re
  48. import errno
  49. import time
  50. import select
  51. import random
  52. import socket
  53. from optparse import OptionParser, OptionValueError
  54. import io
  55. import pwd
  56. import posix
  57. import copy
  58. import isc.cc
  59. import isc.util.process
  60. import isc.net.parse
  61. import isc.log
  62. from isc.log_messages.bind10_messages import *
  63. import isc.bind10.component
  64. import isc.bind10.special_component
  65. import isc.bind10.socket_cache
  66. import libutil_io_python
  67. import tempfile
  68. isc.log.init("b10-boss")
  69. logger = isc.log.Logger("boss")
  70. # Pending system-wide debug level definitions, the ones we
  71. # use here are hardcoded for now
  72. DBG_PROCESS = logger.DBGLVL_TRACE_BASIC
  73. DBG_COMMANDS = logger.DBGLVL_TRACE_DETAIL
  74. # Messages sent over the unix domain socket to indicate if it is followed by a real socket
  75. CREATOR_SOCKET_OK = "1\n"
  76. CREATOR_SOCKET_UNAVAILABLE = "0\n"
  77. # Assign this process some longer name
  78. isc.util.process.rename(sys.argv[0])
  79. # This is the version that gets displayed to the user.
  80. # The VERSION string consists of the module name, the module version
  81. # number, and the overall BIND 10 version number (set in configure.ac).
  82. VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
  83. # This is for boot_time of Boss
  84. _BASETIME = time.gmtime()
  85. class ProcessInfoError(Exception): pass
  86. class ProcessInfo:
  87. """Information about a process"""
  88. dev_null = open(os.devnull, "w")
  89. def __init__(self, name, args, env={}, dev_null_stdout=False,
  90. dev_null_stderr=False, uid=None, username=None):
  91. self.name = name
  92. self.args = args
  93. self.env = env
  94. self.dev_null_stdout = dev_null_stdout
  95. self.dev_null_stderr = dev_null_stderr
  96. self.uid = uid
  97. self.username = username
  98. self.process = None
  99. self.pid = None
  100. def _preexec_work(self):
  101. """Function used before running a program that needs to run as a
  102. different user."""
  103. # First, put us into a separate process group so we don't get
  104. # SIGINT signals on Ctrl-C (the boss will shut everthing down by
  105. # other means).
  106. os.setpgrp()
  107. # Second, set the user ID if one has been specified
  108. if self.uid is not None:
  109. try:
  110. posix.setuid(self.uid)
  111. except OSError as e:
  112. if e.errno == errno.EPERM:
  113. # if we failed to change user due to permission report that
  114. raise ProcessInfoError("Unable to change to user %s (uid %d)" % (self.username, self.uid))
  115. else:
  116. # otherwise simply re-raise whatever error we found
  117. raise
  118. def _spawn(self):
  119. if self.dev_null_stdout:
  120. spawn_stdout = self.dev_null
  121. else:
  122. spawn_stdout = None
  123. if self.dev_null_stderr:
  124. spawn_stderr = self.dev_null
  125. else:
  126. spawn_stderr = None
  127. # Environment variables for the child process will be a copy of those
  128. # of the boss process with any additional specific variables given
  129. # on construction (self.env).
  130. spawn_env = copy.deepcopy(os.environ)
  131. spawn_env.update(self.env)
  132. if ADD_LIBEXEC_PATH:
  133. spawn_env['PATH'] = "@@LIBEXECDIR@@:" + spawn_env['PATH']
  134. self.process = subprocess.Popen(self.args,
  135. stdin=subprocess.PIPE,
  136. stdout=spawn_stdout,
  137. stderr=spawn_stderr,
  138. close_fds=True,
  139. env=spawn_env,
  140. preexec_fn=self._preexec_work)
  141. self.pid = self.process.pid
  142. # spawn() and respawn() are the same for now, but in the future they
  143. # may have different functionality
  144. def spawn(self):
  145. self._spawn()
  146. def respawn(self):
  147. self._spawn()
  148. class CChannelConnectError(Exception): pass
  149. class ProcessStartError(Exception): pass
  150. class BoB:
  151. """Boss of BIND class."""
  152. def __init__(self, msgq_socket_file=None, data_path=None,
  153. config_filename=None, nocache=False, verbose=False, setuid=None,
  154. username=None, cmdctl_port=None, wait_time=10):
  155. """
  156. Initialize the Boss of BIND. This is a singleton (only one can run).
  157. The msgq_socket_file specifies the UNIX domain socket file that the
  158. msgq process listens on. If verbose is True, then the boss reports
  159. what it is doing.
  160. Data path and config filename are passed through to config manager
  161. (if provided) and specify the config file to be used.
  162. The cmdctl_port is passed to cmdctl and specify on which port it
  163. should listen.
  164. wait_time controls the amount of time (in seconds) that Boss waits
  165. for selected processes to initialize before continuing with the
  166. initialization. Currently this is only the configuration manager.
  167. """
  168. self.cc_session = None
  169. self.ccs = None
  170. self.curproc = None
  171. self.msgq_socket_file = msgq_socket_file
  172. self.nocache = nocache
  173. self.component_config = {}
  174. # Some time in future, it may happen that a single component has
  175. # multple processes. If so happens, name "components" may be
  176. # inapropriate. But as the code isn't probably completely ready
  177. # for it, we leave it at components for now.
  178. self.components = {}
  179. # Simply list of components that died and need to wait for a
  180. # restart. Components manage their own restart schedule now
  181. self.components_to_restart = []
  182. self.runnable = False
  183. self.uid = setuid
  184. self.username = username
  185. self.verbose = verbose
  186. self.data_path = data_path
  187. self.config_filename = config_filename
  188. self.cmdctl_port = cmdctl_port
  189. self.wait_time = wait_time
  190. self._component_configurator = isc.bind10.component.Configurator(self,
  191. isc.bind10.special_component.get_specials())
  192. # The priorities here make them start in the correct order. First
  193. # the socket creator (which would drop root privileges by then),
  194. # then message queue and after that the config manager (which uses
  195. # the config manager)
  196. self.__core_components = {
  197. 'sockcreator': {
  198. 'kind': 'core',
  199. 'special': 'sockcreator',
  200. 'priority': 200
  201. },
  202. 'msgq': {
  203. 'kind': 'core',
  204. 'special': 'msgq',
  205. 'priority': 199
  206. },
  207. 'cfgmgr': {
  208. 'kind': 'core',
  209. 'special': 'cfgmgr',
  210. 'priority': 198
  211. }
  212. }
  213. self.__started = False
  214. self.exitcode = 0
  215. # If -v was set, enable full debug logging.
  216. if self.verbose:
  217. logger.set_severity("DEBUG", 99)
  218. # This is set in init_socket_srv
  219. self._socket_path = None
  220. self._socket_cache = None
  221. self._tmpdir = None
  222. self._srv_socket = None
  223. self._unix_sockets = {}
  224. def __propagate_component_config(self, config):
  225. comps = dict(config)
  226. # Fill in the core components, so they stay alive
  227. for comp in self.__core_components:
  228. if comp in comps:
  229. raise Exception(comp + " is core component managed by " +
  230. "bind10 boss, do not set it")
  231. comps[comp] = self.__core_components[comp]
  232. # Update the configuration
  233. self._component_configurator.reconfigure(comps)
  234. def config_handler(self, new_config):
  235. # If this is initial update, don't do anything now, leave it to startup
  236. if not self.runnable:
  237. return
  238. logger.debug(DBG_COMMANDS, BIND10_RECEIVED_NEW_CONFIGURATION,
  239. new_config)
  240. try:
  241. if 'components' in new_config:
  242. self.__propagate_component_config(new_config['components'])
  243. return isc.config.ccsession.create_answer(0)
  244. except Exception as e:
  245. return isc.config.ccsession.create_answer(1, str(e))
  246. def get_processes(self):
  247. pids = list(self.components.keys())
  248. pids.sort()
  249. process_list = [ ]
  250. for pid in pids:
  251. process_list.append([pid, self.components[pid].name()])
  252. return process_list
  253. def _get_stats_data(self):
  254. return { "owner": "Boss",
  255. "data": { 'boot_time':
  256. time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
  257. }
  258. }
  259. def command_handler(self, command, args):
  260. logger.debug(DBG_COMMANDS, BIND10_RECEIVED_COMMAND, command)
  261. answer = isc.config.ccsession.create_answer(1, "command not implemented")
  262. if type(command) != str:
  263. answer = isc.config.ccsession.create_answer(1, "bad command")
  264. else:
  265. if command == "shutdown":
  266. self.runnable = False
  267. answer = isc.config.ccsession.create_answer(0)
  268. elif command == "getstats":
  269. answer = isc.config.ccsession.create_answer(0, self._get_stats_data())
  270. elif command == "sendstats":
  271. # send statistics data to the stats daemon immediately
  272. stats_data = self._get_stats_data()
  273. valid = self.ccs.get_module_spec().validate_statistics(
  274. True, stats_data["data"])
  275. if valid:
  276. cmd = isc.config.ccsession.create_command('set', stats_data)
  277. seq = self.cc_session.group_sendmsg(cmd, 'Stats')
  278. # Consume the answer, in case it becomes a orphan message.
  279. try:
  280. self.cc_session.group_recvmsg(False, seq)
  281. except isc.cc.session.SessionTimeout:
  282. pass
  283. answer = isc.config.ccsession.create_answer(0)
  284. else:
  285. logger.fatal(BIND10_INVALID_STATISTICS_DATA);
  286. answer = isc.config.ccsession.create_answer(
  287. 1, "specified statistics data is invalid")
  288. elif command == "ping":
  289. answer = isc.config.ccsession.create_answer(0, "pong")
  290. elif command == "show_processes":
  291. answer = isc.config.ccsession. \
  292. create_answer(0, self.get_processes())
  293. elif command == "get_socket":
  294. answer = self._get_socket(args)
  295. elif command == "drop_socket":
  296. if "token" not in args:
  297. answer = isc.config.ccsession. \
  298. create_answer(1, "Missing token parameter")
  299. else:
  300. try:
  301. self._socket_cache.drop_socket(args["token"])
  302. answer = isc.config.ccsession.create_answer(0)
  303. except Exception as e:
  304. answer = isc.config.ccsession.create_answer(1, str(e))
  305. else:
  306. answer = isc.config.ccsession.create_answer(1,
  307. "Unknown command")
  308. return answer
  309. def kill_started_components(self):
  310. """
  311. Called as part of the exception handling when a process fails to
  312. start, this runs through the list of started processes, killing
  313. each one. It then clears that list.
  314. """
  315. logger.info(BIND10_KILLING_ALL_PROCESSES)
  316. for pid in self.components:
  317. logger.info(BIND10_KILL_PROCESS, self.components[pid].name())
  318. self.components[pid].kill(True)
  319. self.components = {}
  320. def _read_bind10_config(self):
  321. """
  322. Reads the parameters associated with the BoB module itself.
  323. This means the list of components we should start now.
  324. This could easily be combined into start_all_processes, but
  325. it stays because of historical reasons and because the tests
  326. replace the method sometimes.
  327. """
  328. logger.info(BIND10_READING_BOSS_CONFIGURATION)
  329. config_data = self.ccs.get_full_config()
  330. self.__propagate_component_config(config_data['components'])
  331. def log_starting(self, process, port = None, address = None):
  332. """
  333. A convenience function to output a "Starting xxx" message if the
  334. logging is set to DEBUG with debuglevel DBG_PROCESS or higher.
  335. Putting this into a separate method ensures
  336. that the output form is consistent across all processes.
  337. The process name (passed as the first argument) is put into
  338. self.curproc, and is used to indicate which process failed to
  339. start if there is an error (and is used in the "Started" message
  340. on success). The optional port and address information are
  341. appended to the message (if present).
  342. """
  343. self.curproc = process
  344. if port is None and address is None:
  345. logger.info(BIND10_STARTING_PROCESS, self.curproc)
  346. elif address is None:
  347. logger.info(BIND10_STARTING_PROCESS_PORT, self.curproc,
  348. port)
  349. else:
  350. logger.info(BIND10_STARTING_PROCESS_PORT_ADDRESS,
  351. self.curproc, address, port)
  352. def log_started(self, pid = None):
  353. """
  354. A convenience function to output a 'Started xxxx (PID yyyy)'
  355. message. As with starting_message(), this ensures a consistent
  356. format.
  357. """
  358. if pid is None:
  359. logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS, self.curproc)
  360. else:
  361. logger.debug(DBG_PROCESS, BIND10_STARTED_PROCESS_PID, self.curproc, pid)
  362. def process_running(self, msg, who):
  363. """
  364. Some processes return a message to the Boss after they have
  365. started to indicate that they are running. The form of the
  366. message is a dictionary with contents {"running:", "<process>"}.
  367. This method checks the passed message and returns True if the
  368. "who" process is contained in the message (so is presumably
  369. running). It returns False for all other conditions and will
  370. log an error if appropriate.
  371. """
  372. if msg is not None:
  373. try:
  374. if msg["running"] == who:
  375. return True
  376. else:
  377. logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
  378. except:
  379. logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
  380. return False
  381. # The next few methods start the individual processes of BIND-10. They
  382. # are called via start_all_processes(). If any fail, an exception is
  383. # raised which is caught by the caller of start_all_processes(); this kills
  384. # processes started up to that point before terminating the program.
  385. def start_msgq(self):
  386. """
  387. Start the message queue and connect to the command channel.
  388. """
  389. self.log_starting("b10-msgq")
  390. msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
  391. True, not self.verbose, uid=self.uid,
  392. username=self.username)
  393. msgq_proc.spawn()
  394. self.log_started(msgq_proc.pid)
  395. # Now connect to the c-channel
  396. cc_connect_start = time.time()
  397. while self.cc_session is None:
  398. # if we have been trying for "a while" give up
  399. if (time.time() - cc_connect_start) > 5:
  400. raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
  401. # try to connect, and if we can't wait a short while
  402. try:
  403. self.cc_session = isc.cc.Session(self.msgq_socket_file)
  404. except isc.cc.session.SessionError:
  405. time.sleep(0.1)
  406. # Subscribe to the message queue. The only messages we expect to receive
  407. # on this channel are once relating to process startup.
  408. self.cc_session.group_subscribe("Boss")
  409. return msgq_proc
  410. def start_cfgmgr(self):
  411. """
  412. Starts the configuration manager process
  413. """
  414. self.log_starting("b10-cfgmgr")
  415. args = ["b10-cfgmgr"]
  416. if self.data_path is not None:
  417. args.append("--data-path=" + self.data_path)
  418. if self.config_filename is not None:
  419. args.append("--config-filename=" + self.config_filename)
  420. bind_cfgd = ProcessInfo("b10-cfgmgr", args,
  421. self.c_channel_env, uid=self.uid,
  422. username=self.username)
  423. bind_cfgd.spawn()
  424. self.log_started(bind_cfgd.pid)
  425. # Wait for the configuration manager to start up as subsequent initialization
  426. # cannot proceed without it. The time to wait can be set on the command line.
  427. time_remaining = self.wait_time
  428. msg, env = self.cc_session.group_recvmsg()
  429. while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
  430. logger.debug(DBG_PROCESS, BIND10_WAIT_CFGMGR)
  431. time.sleep(1)
  432. time_remaining = time_remaining - 1
  433. msg, env = self.cc_session.group_recvmsg()
  434. if not self.process_running(msg, "ConfigManager"):
  435. raise ProcessStartError("Configuration manager process has not started")
  436. return bind_cfgd
  437. def start_ccsession(self, c_channel_env):
  438. """
  439. Start the CC Session
  440. The argument c_channel_env is unused but is supplied to keep the
  441. argument list the same for all start_xxx methods.
  442. With regards to logging, note that as the CC session is not a
  443. process, the log_starting/log_started methods are not used.
  444. """
  445. logger.info(BIND10_STARTING_CC)
  446. self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
  447. self.config_handler,
  448. self.command_handler,
  449. socket_file = self.msgq_socket_file)
  450. self.ccs.start()
  451. logger.debug(DBG_PROCESS, BIND10_STARTED_CC)
  452. # A couple of utility methods for starting processes...
  453. def start_process(self, name, args, c_channel_env, port=None, address=None):
  454. """
  455. Given a set of command arguments, start the process and output
  456. appropriate log messages. If the start is successful, the process
  457. is added to the list of started processes.
  458. The port and address arguments are for log messages only.
  459. """
  460. self.log_starting(name, port, address)
  461. newproc = ProcessInfo(name, args, c_channel_env)
  462. newproc.spawn()
  463. self.log_started(newproc.pid)
  464. return newproc
  465. def register_process(self, pid, component):
  466. """
  467. Put another process into boss to watch over it. When the process
  468. dies, the component.failed() is called with the exit code.
  469. It is expected the info is a isc.bind10.component.BaseComponent
  470. subclass (or anything having the same interface).
  471. """
  472. self.components[pid] = component
  473. def start_simple(self, name):
  474. """
  475. Most of the BIND-10 processes are started with the command:
  476. <process-name> [-v]
  477. ... where -v is appended if verbose is enabled. This method
  478. generates the arguments from the name and starts the process.
  479. The port and address arguments are for log messages only.
  480. """
  481. # Set up the command arguments.
  482. args = [name]
  483. if self.verbose:
  484. args += ['-v']
  485. # ... and start the process
  486. return self.start_process(name, args, self.c_channel_env)
  487. # The next few methods start up the rest of the BIND-10 processes.
  488. # Although many of these methods are little more than a call to
  489. # start_simple, they are retained (a) for testing reasons and (b) as a place
  490. # where modifications can be made if the process start-up sequence changes
  491. # for a given process.
  492. def start_auth(self):
  493. """
  494. Start the Authoritative server
  495. """
  496. if self.uid is not None and self.__started:
  497. logger.warn(BIND10_START_AS_NON_ROOT_AUTH)
  498. authargs = ['b10-auth']
  499. if self.nocache:
  500. authargs += ['-n']
  501. if self.uid:
  502. authargs += ['-u', str(self.uid)]
  503. if self.verbose:
  504. authargs += ['-v']
  505. # ... and start
  506. return self.start_process("b10-auth", authargs, self.c_channel_env)
  507. def start_resolver(self):
  508. """
  509. Start the Resolver. At present, all these arguments and switches
  510. are pure speculation. As with the auth daemon, they should be
  511. read from the configuration database.
  512. """
  513. if self.uid is not None and self.__started:
  514. logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
  515. self.curproc = "b10-resolver"
  516. # XXX: this must be read from the configuration manager in the future
  517. resargs = ['b10-resolver']
  518. if self.uid:
  519. resargs += ['-u', str(self.uid)]
  520. if self.verbose:
  521. resargs += ['-v']
  522. # ... and start
  523. return self.start_process("b10-resolver", resargs, self.c_channel_env)
  524. def start_cmdctl(self):
  525. """
  526. Starts the command control process
  527. """
  528. args = ["b10-cmdctl"]
  529. if self.cmdctl_port is not None:
  530. args.append("--port=" + str(self.cmdctl_port))
  531. if self.verbose:
  532. args.append("-v")
  533. return self.start_process("b10-cmdctl", args, self.c_channel_env,
  534. self.cmdctl_port)
  535. def start_all_components(self):
  536. """
  537. Starts up all the components. Any exception generated during the
  538. starting of the components is handled by the caller.
  539. """
  540. # Start the real core (sockcreator, msgq, cfgmgr)
  541. self._component_configurator.startup(self.__core_components)
  542. # Connect to the msgq. This is not a process, so it's not handled
  543. # inside the configurator.
  544. self.start_ccsession(self.c_channel_env)
  545. # Extract the parameters associated with Bob. This can only be
  546. # done after the CC Session is started. Note that the logging
  547. # configuration may override the "-v" switch set on the command line.
  548. self._read_bind10_config()
  549. # TODO: Return the dropping of privileges
  550. def startup(self):
  551. """
  552. Start the BoB instance.
  553. Returns None if successful, otherwise an string describing the
  554. problem.
  555. """
  556. # Try to connect to the c-channel daemon, to see if it is already
  557. # running
  558. c_channel_env = {}
  559. if self.msgq_socket_file is not None:
  560. c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
  561. logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
  562. # try to connect, and if we can't wait a short while
  563. try:
  564. self.cc_session = isc.cc.Session(self.msgq_socket_file)
  565. logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
  566. return "b10-msgq already running, or socket file not cleaned , cannot start"
  567. except isc.cc.session.SessionError:
  568. # this is the case we want, where the msgq is not running
  569. pass
  570. # Start all components. If any one fails to start, kill all started
  571. # components and exit with an error indication.
  572. try:
  573. self.c_channel_env = c_channel_env
  574. self.start_all_components()
  575. except Exception as e:
  576. self.kill_started_components()
  577. return "Unable to start " + self.curproc + ": " + str(e)
  578. # Started successfully
  579. self.runnable = True
  580. self.__started = True
  581. return None
  582. def stop_process(self, process, recipient):
  583. """
  584. Stop the given process, friendly-like. The process is the name it has
  585. (in logs, etc), the recipient is the address on msgq.
  586. """
  587. logger.info(BIND10_STOP_PROCESS, process)
  588. self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
  589. recipient)
  590. def component_shutdown(self, exitcode=0):
  591. """
  592. Stop the Boss instance from a components' request. The exitcode
  593. indicates the desired exit code.
  594. If we did not start yet, it raises an exception, which is meant
  595. to propagate through the component and configurator to the startup
  596. routine and abort the startup immediately. If it is started up already,
  597. we just mark it so we terminate soon.
  598. It does set the exit code in both cases.
  599. """
  600. self.exitcode = exitcode
  601. if not self.__started:
  602. raise Exception("Component failed during startup");
  603. else:
  604. self.runnable = False
  605. def shutdown(self):
  606. """Stop the BoB instance."""
  607. logger.info(BIND10_SHUTDOWN)
  608. # first try using the BIND 10 request to stop
  609. try:
  610. self._component_configurator.shutdown()
  611. except:
  612. pass
  613. # XXX: some delay probably useful... how much is uncertain
  614. # I have changed the delay from 0.5 to 1, but sometime it's
  615. # still not enough.
  616. time.sleep(1)
  617. self.reap_children()
  618. # next try sending a SIGTERM
  619. components_to_stop = list(self.components.values())
  620. for component in components_to_stop:
  621. logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
  622. try:
  623. component.kill()
  624. except OSError:
  625. # ignore these (usually ESRCH because the child
  626. # finally exited)
  627. pass
  628. # finally, send SIGKILL (unmaskable termination) until everybody dies
  629. while self.components:
  630. # XXX: some delay probably useful... how much is uncertain
  631. time.sleep(0.1)
  632. self.reap_children()
  633. components_to_stop = list(self.components.values())
  634. for component in components_to_stop:
  635. logger.info(BIND10_SEND_SIGKILL, component.name(),
  636. component.pid())
  637. try:
  638. component.kill(True)
  639. except OSError:
  640. # ignore these (usually ESRCH because the child
  641. # finally exited)
  642. pass
  643. logger.info(BIND10_SHUTDOWN_COMPLETE)
  644. def _get_process_exit_status(self):
  645. return os.waitpid(-1, os.WNOHANG)
  646. def reap_children(self):
  647. """Check to see if any of our child processes have exited,
  648. and note this for later handling.
  649. """
  650. while True:
  651. try:
  652. (pid, exit_status) = self._get_process_exit_status()
  653. except OSError as o:
  654. if o.errno == errno.ECHILD: break
  655. # XXX: should be impossible to get any other error here
  656. raise
  657. if pid == 0: break
  658. if pid in self.components:
  659. # One of the components we know about. Get information on it.
  660. component = self.components.pop(pid)
  661. logger.info(BIND10_PROCESS_ENDED, component.name(), pid,
  662. exit_status)
  663. if component.running() and self.runnable:
  664. # Tell it it failed. But only if it matters (we are
  665. # not shutting down and the component considers itself
  666. # to be running.
  667. component_restarted = component.failed(exit_status);
  668. # if the process wants to be restarted, but not just yet,
  669. # it returns False
  670. if not component_restarted:
  671. self.components_to_restart.append(component)
  672. else:
  673. logger.info(BIND10_UNKNOWN_CHILD_PROCESS_ENDED, pid)
  674. def restart_processes(self):
  675. """
  676. Restart any dead processes:
  677. * Returns the time when the next process is ready to be restarted.
  678. * If the server is shutting down, returns 0.
  679. * If there are no processes, returns None.
  680. The values returned can be safely passed into select() as the
  681. timeout value.
  682. """
  683. if not self.runnable:
  684. return 0
  685. still_dead = []
  686. # keep track of the first time we need to check this queue again,
  687. # if at all
  688. next_restart_time = None
  689. now = time.time()
  690. for component in self.components_to_restart:
  691. if not component.restart(now):
  692. still_dead.append(component)
  693. if next_restart_time is None or\
  694. next_restart_time > component.get_restart_time():
  695. next_restart_time = component.get_restart_time()
  696. self.components_to_restart = still_dead
  697. return next_restart_time
  698. def _get_socket(self, args):
  699. """
  700. Implementation of the get_socket CC command. It asks the cache
  701. to provide the token and sends the information back.
  702. """
  703. try:
  704. try:
  705. addr = isc.net.parse.addr_parse(args['address'])
  706. port = isc.net.parse.port_parse(args['port'])
  707. protocol = args['protocol']
  708. if protocol not in ['UDP', 'TCP']:
  709. raise ValueError("Protocol must be either UDP or TCP")
  710. share_mode = args['share_mode']
  711. if share_mode not in ['ANY', 'SAMEAPP', 'NO']:
  712. raise ValueError("Share mode must be one of ANY, SAMEAPP" +
  713. " or NO")
  714. share_name = args['share_name']
  715. except KeyError as ke:
  716. return \
  717. isc.config.ccsession.create_answer(1,
  718. "Missing parameter " +
  719. str(ke))
  720. # FIXME: This call contains blocking IPC. It is expected to be
  721. # short, but if it turns out to be problem, we'll need to do
  722. # something about it.
  723. token = self._socket_cache.get_token(protocol, addr, port,
  724. share_mode, share_name)
  725. return isc.config.ccsession.create_answer(0, {
  726. 'token': token,
  727. 'path': self._socket_path
  728. })
  729. except Exception as e:
  730. return isc.config.ccsession.create_answer(1, str(e))
  731. def socket_request_handler(self, token, unix_socket):
  732. """
  733. This function handles a token that comes over a unix_domain socket.
  734. The function looks into the _socket_cache and sends the socket
  735. identified by the token back over the unix_socket.
  736. """
  737. try:
  738. fd = self._socket_cache.get_socket(token, unix_socket.fileno())
  739. # FIXME: These two calls are blocking in their nature. An OS-level
  740. # buffer is likely to be large enough to hold all these data, but
  741. # if it wasn't and the remote application got stuck, we would have
  742. # a problem. If there appear such problems, we should do something
  743. # about it.
  744. unix_socket.sendall(CREATOR_SOCKET_OK)
  745. libutil_io_python.send_fd(unix_socket.fileno(), fd)
  746. except Exception as e:
  747. logger.info(BIND10_NO_SOCKET, token, e)
  748. unix_socket.sendall(CREATOR_SOCKET_UNAVAILABLE)
  749. def socket_consumer_dead(self, unix_socket):
  750. """
  751. This function handles when a unix_socket closes. This means all
  752. sockets sent to it are to be considered closed. This function signals
  753. so to the _socket_cache.
  754. """
  755. logger.info(BIND10_LOST_SOCKET_CONSUMER, unix_socket.fileno())
  756. try:
  757. self._socket_cache.drop_application(unix_socket.fileno())
  758. except ValueError:
  759. # This means the application holds no sockets. It's harmless, as it
  760. # can happen in real life - for example, it requests a socket, but
  761. # get_socket doesn't find it, so the application dies. It should be
  762. # rare, though.
  763. pass
  764. def set_creator(self, creator):
  765. """
  766. Registeres a socket creator into the boss. The socket creator is not
  767. used directly, but through a cache. The cache is created in this
  768. method.
  769. If called more than once, it raises a ValueError.
  770. """
  771. if self._socket_cache is not None:
  772. raise ValueError("A creator was inserted previously")
  773. self._socket_cache = isc.bind10.socket_cache.Cache(creator)
  774. def init_socket_srv(self):
  775. """
  776. Creates and listens on a unix-domain socket to be able to send out
  777. the sockets.
  778. This method should be called after switching user, or the switched
  779. applications won't be able to access the socket.
  780. """
  781. self._srv_socket = socket.socket(socket.AF_UNIX)
  782. # We create a temporary directory somewhere safe and unique, to avoid
  783. # the need to find the place ourself or bother users. Also, this
  784. # secures the socket on some platforms, as it creates a private
  785. # directory.
  786. self._tmpdir = tempfile.mkdtemp()
  787. # Get the name
  788. self._socket_path = os.path.join(self._tmpdir, "sockcreator")
  789. # And bind the socket to the name
  790. self._srv_socket.bind(self._socket_path)
  791. self._srv_socket.listen(5)
  792. def remove_socket_srv(self):
  793. """
  794. Closes and removes the listening socket and the directory where it
  795. lives, as we created both.
  796. It does nothing if the _srv_socket is not set (eg. it was not yet
  797. initialized).
  798. """
  799. if self._srv_socket is not None:
  800. self._srv_socket.close()
  801. os.remove(self._socket_path)
  802. os.rmdir(self._tmpdir)
  803. def _srv_accept(self):
  804. """
  805. Accept a socket from the unix domain socket server and put it to the
  806. others we care about.
  807. """
  808. socket = self._srv_socket.accept()
  809. self._unix_sockets[socket.fileno()] = (socket, b'')
  810. def _socket_data(self, socket_fileno):
  811. """
  812. This is called when a socket identified by the socket_fileno needs
  813. attention. We try to read data from there. If it is closed, we remove
  814. it.
  815. """
  816. (sock, previous) = self._unix_sockets[socket_fileno]
  817. while True:
  818. try:
  819. data = sock.recv(1, socket.MSG_DONTWAIT)
  820. except socket.error as se:
  821. # These two might be different on some systems
  822. if se.errno == errno.EAGAIN or se.errno == errno.EWOULDBLOCK:
  823. # No more data now. Oh, well, just store what we have.
  824. self._unix_sockets[socket_fileno] = (sock, previous)
  825. return
  826. else:
  827. data = b'' # Pretend it got closed
  828. if len(data) == 0: # The socket got to it's end
  829. del self._unix_sockets[socket_fileno]
  830. self.socket_consumer_dead(sock)
  831. sock.close()
  832. return
  833. else:
  834. if data == b"\n":
  835. # Handle this token and clear it
  836. self.socket_request_handler(previous, sock)
  837. previous = b''
  838. else:
  839. previous += data
  840. def run(self, wakeup_fd):
  841. """
  842. The main loop, waiting for sockets, commands and dead processes.
  843. Runs as long as the runnable is true.
  844. The wakeup_fd descriptor is the read end of pipe where CHLD signal
  845. handler writes.
  846. """
  847. ccs_fd = self.ccs.get_socket().fileno()
  848. while self.runnable:
  849. # clean up any processes that exited
  850. self.reap_children()
  851. next_restart = self.restart_processes()
  852. if next_restart is None:
  853. wait_time = None
  854. else:
  855. wait_time = max(next_restart - time.time(), 0)
  856. # select() can raise EINTR when a signal arrives,
  857. # even if they are resumable, so we have to catch
  858. # the exception
  859. try:
  860. (rlist, wlist, xlist) = \
  861. select.select([wakeup_fd, ccs_fd,
  862. self._srv_socket.fileno()] +
  863. list(self._unix_sockets.keys()), [], [],
  864. wait_time)
  865. except select.error as err:
  866. if err.args[0] == errno.EINTR:
  867. (rlist, wlist, xlist) = ([], [], [])
  868. else:
  869. logger.fatal(BIND10_SELECT_ERROR, err)
  870. break
  871. for fd in rlist + xlist:
  872. if fd == ccs_fd:
  873. try:
  874. self.ccs.check_command()
  875. except isc.cc.session.ProtocolError:
  876. logger.fatal(BIND10_MSGQ_DISAPPEARED)
  877. self.runnable = False
  878. break
  879. elif fd == wakeup_fd:
  880. os.read(wakeup_fd, 32)
  881. elif fd == self._srv_socket.fileno():
  882. self._srv_accept()
  883. elif fd in self._unix_sockets:
  884. self._socket_data(fd)
  885. # global variables, needed for signal handlers
  886. options = None
  887. boss_of_bind = None
  888. def reaper(signal_number, stack_frame):
  889. """A child process has died (SIGCHLD received)."""
  890. # don't do anything...
  891. # the Python signal handler has been set up to write
  892. # down a pipe, waking up our select() bit
  893. pass
  894. def get_signame(signal_number):
  895. """Return the symbolic name for a signal."""
  896. for sig in dir(signal):
  897. if sig.startswith("SIG") and sig[3].isalnum():
  898. if getattr(signal, sig) == signal_number:
  899. return sig
  900. return "Unknown signal %d" % signal_number
  901. # XXX: perhaps register atexit() function and invoke that instead
  902. def fatal_signal(signal_number, stack_frame):
  903. """We need to exit (SIGINT or SIGTERM received)."""
  904. global options
  905. global boss_of_bind
  906. logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
  907. signal.signal(signal.SIGCHLD, signal.SIG_DFL)
  908. boss_of_bind.runnable = False
  909. def process_rename(option, opt_str, value, parser):
  910. """Function that renames the process if it is requested by a option."""
  911. isc.util.process.rename(value)
  912. def parse_args(args=sys.argv[1:], Parser=OptionParser):
  913. """
  914. Function for parsing command line arguments. Returns the
  915. options object from OptionParser.
  916. """
  917. parser = Parser(version=VERSION)
  918. parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
  919. type="string", default=None,
  920. help="UNIX domain socket file the b10-msgq daemon will use")
  921. parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
  922. default=False, help="disable hot-spot cache in authoritative DNS server")
  923. parser.add_option("-u", "--user", dest="user", type="string", default=None,
  924. help="Change user after startup (must run as root)")
  925. parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
  926. help="display more about what is going on")
  927. parser.add_option("--pretty-name", type="string", action="callback",
  928. callback=process_rename,
  929. help="Set the process name (displayed in ps, top, ...)")
  930. parser.add_option("-c", "--config-file", action="store",
  931. dest="config_file", default=None,
  932. help="Configuration database filename")
  933. parser.add_option("-p", "--data-path", dest="data_path",
  934. help="Directory to search for configuration files",
  935. default=None)
  936. parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
  937. default=None, help="Port of command control")
  938. parser.add_option("--pid-file", dest="pid_file", type="string",
  939. default=None,
  940. help="file to dump the PID of the BIND 10 process")
  941. parser.add_option("-w", "--wait", dest="wait_time", type="int",
  942. default=10, help="Time (in seconds) to wait for config manager to start up")
  943. (options, args) = parser.parse_args(args)
  944. if options.cmdctl_port is not None:
  945. try:
  946. isc.net.parse.port_parse(options.cmdctl_port)
  947. except ValueError as e:
  948. parser.error(e)
  949. if args:
  950. parser.print_help()
  951. sys.exit(1)
  952. return options
  953. def dump_pid(pid_file):
  954. """
  955. Dump the PID of the current process to the specified file. If the given
  956. file is None this function does nothing. If the file already exists,
  957. the existing content will be removed. If a system error happens in
  958. creating or writing to the file, the corresponding exception will be
  959. propagated to the caller.
  960. """
  961. if pid_file is None:
  962. return
  963. f = open(pid_file, "w")
  964. f.write('%d\n' % os.getpid())
  965. f.close()
  966. def unlink_pid_file(pid_file):
  967. """
  968. Remove the given file, which is basically expected to be the PID file
  969. created by dump_pid(). The specified may or may not exist; if it
  970. doesn't this function does nothing. Other system level errors in removing
  971. the file will be propagated as the corresponding exception.
  972. """
  973. if pid_file is None:
  974. return
  975. try:
  976. os.unlink(pid_file)
  977. except OSError as error:
  978. if error.errno is not errno.ENOENT:
  979. raise
  980. def main():
  981. global options
  982. global boss_of_bind
  983. # Enforce line buffering on stdout, even when not a TTY
  984. sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
  985. options = parse_args()
  986. # Check user ID.
  987. setuid = None
  988. username = None
  989. if options.user:
  990. # Try getting information about the user, assuming UID passed.
  991. try:
  992. pw_ent = pwd.getpwuid(int(options.user))
  993. setuid = pw_ent.pw_uid
  994. username = pw_ent.pw_name
  995. except ValueError:
  996. pass
  997. except KeyError:
  998. pass
  999. # Next try getting information about the user, assuming user name
  1000. # passed.
  1001. # If the information is both a valid user name and user number, we
  1002. # prefer the name because we try it second. A minor point, hopefully.
  1003. try:
  1004. pw_ent = pwd.getpwnam(options.user)
  1005. setuid = pw_ent.pw_uid
  1006. username = pw_ent.pw_name
  1007. except KeyError:
  1008. pass
  1009. if setuid is None:
  1010. logger.fatal(BIND10_INVALID_USER, options.user)
  1011. sys.exit(1)
  1012. # Announce startup.
  1013. logger.info(BIND10_STARTING, VERSION)
  1014. # Create wakeup pipe for signal handlers
  1015. wakeup_pipe = os.pipe()
  1016. signal.set_wakeup_fd(wakeup_pipe[1])
  1017. # Set signal handlers for catching child termination, as well
  1018. # as our own demise.
  1019. signal.signal(signal.SIGCHLD, reaper)
  1020. signal.siginterrupt(signal.SIGCHLD, False)
  1021. signal.signal(signal.SIGINT, fatal_signal)
  1022. signal.signal(signal.SIGTERM, fatal_signal)
  1023. # Block SIGPIPE, as we don't want it to end this process
  1024. signal.signal(signal.SIGPIPE, signal.SIG_IGN)
  1025. try:
  1026. # Go bob!
  1027. boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
  1028. options.config_file, options.nocache,
  1029. options.verbose, setuid, username,
  1030. options.cmdctl_port, options.wait_time)
  1031. startup_result = boss_of_bind.startup()
  1032. if startup_result:
  1033. logger.fatal(BIND10_STARTUP_ERROR, startup_result)
  1034. sys.exit(1)
  1035. boss_of_bind.init_socket_srv()
  1036. logger.info(BIND10_STARTUP_COMPLETE)
  1037. dump_pid(options.pid_file)
  1038. # Let it run
  1039. boss_of_bind.run(wakeup_pipe[0])
  1040. # shutdown
  1041. signal.signal(signal.SIGCHLD, signal.SIG_DFL)
  1042. boss_of_bind.shutdown()
  1043. finally:
  1044. # Clean up the filesystem
  1045. unlink_pid_file(options.pid_file)
  1046. if boss_of_bind is not None:
  1047. boss_of_bind.remove_socket_srv()
  1048. sys.exit(boss_of_bind.exitcode)
  1049. if __name__ == "__main__":
  1050. main()