Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

#!/usr/bin/python3 

 

# Copyright (C) 2011  Internet Systems Consortium. 

# 

# Permission to use, copy, modify, and distribute this software for any 

# purpose with or without fee is hereby granted, provided that the above 

# copyright notice and this permission notice appear in all copies. 

# 

# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM 

# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 

# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL 

# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, 

# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING 

# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 

# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 

# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 

 

 

import sys; sys.path.append ('@@PYTHONPATH@@') 

import isc 

import bind10_config 

from isc.dns import * 

from isc.config.ccsession import * 

from isc.cc import SessionError, SessionTimeout 

import isc.util.process 

import isc.util.cio.socketsession 

import select 

import errno 

 

from isc.log_messages.ddns_messages import * 

 

from optparse import OptionParser, OptionValueError 

import os 

import os.path 

import signal 

import socket 

 

isc.log.init("b10-ddns") 

logger = isc.log.Logger("ddns") 

TRACE_BASIC = logger.DBGLVL_TRACE_BASIC 

 

DATA_PATH = bind10_config.DATA_PATH 

SOCKET_FILE = DATA_PATH + '/ddns_socket' 

46if "B10_FROM_SOURCE" in os.environ: 

    DATA_PATH = os.environ['B10_FROM_SOURCE'] + "/src/bin/ddns" 

52if "B10_FROM_BUILD" in os.environ: 

48    if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ: 

        SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \ 

            "/ddns_socket" 

    else: 

        SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/ddns_socket" 

SPECFILE_LOCATION = DATA_PATH + "/ddns.spec" 

 

 

isc.util.process.rename() 

 

class DDNSConfigError(Exception): 

    '''An exception indicating an error in updating ddns configuration. 

 

    This exception is raised when the ddns process encounters an error in 

    handling configuration updates.  Not all syntax error can be caught 

    at the module-CC layer, so ddns needs to (explicitly or implicitly) 

    validate the given configuration data itself.  When it finds an error 

    it raises this exception (either directly or by converting an exception 

    from other modules) as a unified error in configuration. 

    ''' 

    pass 

 

class DDNSSessionError(Exception): 

    '''An exception raised for some unexpected events during a ddns session. 

    ''' 

    pass 

 

class DDNSSession: 

    '''Class to handle one DDNS update''' 

 

    def __init__(self): 

        '''Initialize a DDNS Session''' 

        pass 

 

def clear_socket(): 

    ''' 

    Removes the socket file, if it exists. 

    ''' 

    if os.path.exists(SOCKET_FILE): 

        os.remove(SOCKET_FILE) 

 

class DDNSServer: 

    def __init__(self, cc_session=None): 

        ''' 

        Initialize the DDNS Server. 

        This sets up a ModuleCCSession for the BIND 10 system. 

        Parameters: 

        cc_session: If None (default), a new ModuleCCSession will be set up. 

                    If specified, the given session will be used. This is 

                    mainly used for testing. 

        ''' 

101        if cc_session is not None: 

            self._cc = cc_session 

        else: 

            self._cc = isc.config.ModuleCCSession(SPECFILE_LOCATION, 

                                                  self.config_handler, 

                                                  self.command_handler) 

 

        self._config_data = self._cc.get_full_config() 

        self._cc.start() 

        self._shutdown = False 

        # List of the session receivers where we get the requests 

        self._socksession_receivers = {} 

        clear_socket() 

        self._listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 

        self._listen_socket.bind(SOCKET_FILE) 

        self._listen_socket.listen(16) 

 

    def config_handler(self, new_config): 

        '''Update config data.''' 

        # TODO: Handle exceptions and turn them to an error response 

        # (once we have any configuration) 

        answer = create_answer(0) 

        return answer 

 

    def command_handler(self, cmd, args): 

        ''' 

        Handle a CC session command, as sent from bindctl or other 

        BIND 10 modules. 

        ''' 

        # TODO: Handle exceptions and turn them to an error response 

        if cmd == "shutdown": 

            logger.info(DDNS_RECEIVED_SHUTDOWN_COMMAND) 

            self.trigger_shutdown() 

            answer = create_answer(0) 

        else: 

            answer = create_answer(1, "Unknown command: " + str(cmd)) 

        return answer 

 

    def trigger_shutdown(self): 

        '''Initiate a shutdown sequence. 

 

        This method is expected to be called in various ways including 

        in the middle of a signal handler, and is designed to be as simple 

        as possible to minimize side effects.  Actual shutdown will take 

        place in a normal control flow. 

 

        ''' 

        logger.info(DDNS_SHUTDOWN) 

        self._shutdown = True 

 

    def shutdown_cleanup(self): 

        ''' 

        Perform any cleanup that is necessary when shutting down the server. 

        Do NOT call this to initialize shutdown, use trigger_shutdown(). 

 

        Currently, it only causes the ModuleCCSession to send a message that 

        this module is stopping. 

        ''' 

        self._cc.send_stopping() 

 

    def accept(self): 

        """ 

        Accept another connection and create the session receiver. 

        """ 

        try: 

            sock = self._listen_socket.accept() 

            fileno = sock.fileno() 

            logger.debug(TRACE_BASIC, DDNS_NEW_CONN, fileno, 

                         sock.getpeername()) 

            receiver = isc.util.cio.socketsession.SocketSessionReceiver(sock) 

            self._socksession_receivers[fileno] = (sock, receiver) 

        except (socket.error, isc.util.cio.socketsession.SocketSessionError) \ 

            as e: 

            # These exceptions mean the connection didn't work, but we can 

            # continue with the rest 

            logger.error(DDNS_ACCEPT_FAILURE, e) 

 

    def handle_request(self, request): 

        """ 

        This is the place where the actual DDNS processing is done. Other 

        methods are either subroutines of this method or methods doing the 

        uninteresting "accounting" stuff, like accepting socket, 

        initialization, etc. 

 

        It is called with the request being session as received from 

        SocketSessionReceiver, i.e. tuple 

        (socket, local_address, remote_address, data). 

        """ 

        # TODO: Implement the magic 

 

        # TODO: Don't propagate most of the exceptions (like datasrc errors), 

        # just drop the packet. 

        pass 

 

    def handle_session(self, fileno): 

        """ 

        Handle incoming session on the socket with given fileno. 

        """ 

        logger.debug(TRACE_BASIC, DDNS_SESSION, fileno) 

        (socket, receiver) = self._socksession_receivers[fileno] 

        try: 

            self.handle_request(receiver.pop()) 

        except isc.util.cio.socketsession.SocketSessionError as se: 

            # No matter why this failed, the connection is in unknown, possibly 

            # broken state. So, we close the socket and remove the receiver. 

            del self._socksession_receivers[fileno] 

            socket.close() 

            logger.warn(DDNS_DROP_CONN, fileno, se) 

 

    def run(self): 

        ''' 

        Get and process all commands sent from cfgmgr or other modules. 

        This loops waiting for events until self.shutdown() has been called. 

        ''' 

        logger.info(DDNS_RUNNING) 

        cc_fileno = self._cc.get_socket().fileno() 

        listen_fileno = self._listen_socket.fileno() 

        while not self._shutdown: 

            # In this event loop, we propagate most of exceptions, which will 

            # subsequently kill the process. We expect the handling functions 

            # to catch their own exceptions which they can recover from 

            # (malformed packets, lost connections, etc). The rationale behind 

            # this is they know best which exceptions are recoverable there 

            # and an exception may be recoverable somewhere, but not elsewhere. 

 

            try: 

                (reads, writes, exceptions) = \ 

                    select.select([cc_fileno, listen_fileno] + 

                                  list(self._socksession_receivers.keys()), [], 

                                  []) 

            except select.error as se: 

                # In case it is just interrupted, we continue like nothing 

                # happened 

                if se.args[0] == errno.EINTR: 

242                    (reads, writes, exceptions) = ([], [], []) 

                else: 

                    raise 

            for fileno in reads: 

                if fileno == cc_fileno: 

                    self._cc.check_command(True) 

                elif fileno == listen_fileno: 

                    self.accept() 

                else: 

                    self.handle_session(fileno) 

        self.shutdown_cleanup() 

        logger.info(DDNS_STOPPED) 

 

def create_signal_handler(ddns_server): 

    ''' 

    This creates a signal_handler for use in set_signal_handler, which 

    shuts down the given DDNSServer (or any object that has a shutdown() 

    method) 

    ''' 

    def signal_handler(signal, frame): 

        ''' 

        Handler for process signals. Since only signals to shut down are sent 

        here, the actual signal is not checked and the server is simply shut 

        down. 

        ''' 

        ddns_server.trigger_shutdown() 

    return signal_handler 

 

def set_signal_handler(signal_handler): 

    ''' 

    Sets the signal handler(s). 

    ''' 

    signal.signal(signal.SIGTERM, signal_handler) 

    signal.signal(signal.SIGINT, signal_handler) 

 

def set_cmd_options(parser): 

    ''' 

    Helper function to set command-line options 

    ''' 

    parser.add_option("-v", "--verbose", dest="verbose", action="store_true", 

            help="display more about what is going on") 

 

def main(ddns_server=None): 

    ''' 

    The main function. 

    Parameters: 

    ddns_server: If None (default), a DDNSServer object is initialized. 

                 If specified, the given DDNSServer will be used. This is 

                 mainly used for testing. 

    cc_session: If None (default), a new ModuleCCSession will be set up. 

                If specified, the given session will be used. This is 

                mainly used for testing. 

    ''' 

    try: 

        parser = OptionParser() 

        set_cmd_options(parser) 

        (options, args) = parser.parse_args() 

290        if options.verbose: 

            print("[b10-ddns] Warning: -v verbose option is ignored at this point.") 

 

293        if ddns_server is None: 

            ddns_server = DDNSServer() 

        set_signal_handler(create_signal_handler(ddns_server)) 

        ddns_server.run() 

    except KeyboardInterrupt: 

        logger.info(DDNS_STOPPED_BY_KEYBOARD) 

    except SessionError as e: 

        logger.error(DDNS_CC_SESSION_ERROR, str(e)) 

    except ModuleCCSessionError as e: 

        logger.error(DDNS_MODULECC_SESSION_ERROR, str(e)) 

    except DDNSConfigError as e: 

        logger.error(DDNS_CONFIG_ERROR, str(e)) 

    except SessionTimeout as e: 

        logger.error(DDNS_CC_SESSION_TIMEOUT_ERROR) 

    except Exception as e: 

        logger.error(DDNS_UNCAUGHT_EXCEPTION, type(e).__name__, str(e)) 

    clear_socket() 

 

311if '__main__' == __name__: 

    main()