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

# Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC") 

# 

# 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 socket 

import struct 

import os 

import copy 

import subprocess 

import copy 

from isc.log_messages.bind10_messages import * 

from libutil_io_python import recv_fd 

 

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

 

""" 

Module that comunicates with the privileged socket creator (b10-sockcreator). 

""" 

 

class CreatorError(Exception): 

    """ 

    Exception for socket creator related errors. 

 

    It has two members: fatal and errno and they are just holding the values 

    passed to the __init__ function. 

    """ 

 

    def __init__(self, message, fatal, errno=None): 

        """ 

        Creates the exception. The message argument is the usual string. 

        The fatal one tells if the error is fatal (eg. the creator crashed) 

        and errno is the errno value returned from socket creator, if 

        applicable. 

        """ 

        Exception.__init__(self, message) 

        self.fatal = fatal 

        self.errno = errno 

 

class Parser: 

    """ 

    This class knows the sockcreator language. It creates commands, sends them 

    and receives the answers and parses them. 

 

    It does not start it, the communication channel must be provided. 

 

    In theory, anything here can throw a fatal CreatorError exception, but it 

    happens only in case something like the creator process crashes. Any other 

    occasions are mentioned explicitly. 

    """ 

 

    def __init__(self, creator_socket): 

        """ 

        Creates the parser. The creator_socket is socket to the socket creator 

        process that will be used for communication. However, the object must 

        have a read_fd() method to read the file descriptor. This slightly 

        unusual trick with modifying an object is used to easy up testing. 

 

        You can use WrappedSocket in production code to add the method to any 

        ordinary socket. 

        """ 

        self.__socket = creator_socket 

        logger.info(BIND10_SOCKCREATOR_INIT) 

 

    def terminate(self): 

        """ 

        Asks the creator process to terminate and waits for it to close the 

        socket. Does not return anything. Raises a CreatorError if there is 

        still data on the socket, if there is an error closing the socket, 

        or if the socket had already been closed. 

        """ 

        if self.__socket is None: 

            raise CreatorError('Terminated already', True) 

        logger.info(BIND10_SOCKCREATOR_TERMINATE) 

        try: 

            self.__socket.sendall(b'T') 

            # Wait for an EOF - it will return empty data 

            eof = self.__socket.recv(1) 

            if len(eof) != 0: 

                raise CreatorError('Protocol error - data after terminated', 

                                   True) 

            self.__socket = None 

        except socket.error as se: 

            self.__socket = None 

            raise CreatorError(str(se), True) 

 

    def get_socket(self, address, port, socktype): 

        """ 

        Asks the socket creator process to create a socket. Pass an address 

        (the isc.net.IPaddr object), port number and socket type (either 

        string "UDP", "TCP" or constant socket.SOCK_DGRAM or 

        socket.SOCK_STREAM. 

 

        Blocks until it is provided by the socket creator process (which 

        should be fast, as it is on localhost) and returns the file descriptor 

        number. It raises a CreatorError exception if the creation fails. 

        """ 

        if self.__socket is None: 

            raise CreatorError('Socket requested on terminated creator', True) 

        # First, assemble the request from parts 

        logger.info(BIND10_SOCKET_GET, address, port, socktype) 

        data = b'S' 

        if socktype == 'UDP' or socktype == socket.SOCK_DGRAM: 

            data += b'U' 

        elif socktype == 'TCP' or socktype == socket.SOCK_STREAM: 

            data += b'T' 

        else: 

            raise ValueError('Unknown socket type: ' + str(socktype)) 

        if address.family == socket.AF_INET: 

            data += b'4' 

124        elif address.family == socket.AF_INET6: 

            data += b'6' 

        else: 

            raise ValueError('Unknown address family in address') 

        data += struct.pack('!H', port) 

        data += address.addr 

        try: 

            # Send the request 

            self.__socket.sendall(data) 

            answer = self.__socket.recv(1) 

            if answer == b'S': 

                # Success! 

                result = self.__socket.read_fd() 

                logger.info(BIND10_SOCKET_CREATED, result) 

                return result 

            elif answer == b'E': 

                # There was an error, read the error as well 

                error = self.__socket.recv(1) 

                errno = struct.unpack('i', 

                                      self.__read_all(len(struct.pack('i', 

                                                                      0)))) 

144                if error == b'S': 

                    cause = 'socket' 

                elif error == b'B': 

                    cause = 'bind' 

                else: 

                    self.__socket = None 

                    logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error) 

                    raise CreatorError('Unknown error cause' + str(answer), True) 

                logger.error(BIND10_SOCKET_ERROR, cause, errno[0], 

                             os.strerror(errno[0])) 

                raise CreatorError('Error creating socket on ' + cause, False, 

                                   errno[0]) 

            else: 

                self.__socket = None 

                logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer) 

                raise CreatorError('Unknown response ' + str(answer), True) 

        except socket.error as se: 

            self.__socket = None 

            logger.fatal(BIND10_SOCKCREATOR_TRANSPORT_ERROR, str(se)) 

            raise CreatorError(str(se), True) 

 

    def __read_all(self, length): 

        """ 

        Keeps reading until length data is read or EOF or error happens. 

 

        EOF is considered error as well and throws a CreatorError. 

        """ 

        result = b'' 

        while len(result) < length: 

            data = self.__socket.recv(length - len(result)) 

173            if len(data) == 0: 

                self.__socket = None 

                logger.fatal(BIND10_SOCKCREATOR_EOF) 

                raise CreatorError('Unexpected EOF', True) 

            result += data 

        return result 

 

class WrappedSocket: 

    """ 

    This class wraps a socket and adds a read_fd method, so it can be used 

    for the Parser class conveniently. It simply copies all its guts into 

    itself and implements the method. 

    """ 

    def __init__(self, socket): 

        # Copy whatever can be copied from the socket 

        for name in dir(socket): 

            if name not in ['__class__', '__weakref__']: 

                setattr(self, name, getattr(socket, name)) 

        # Keep the socket, so we can prevent it from being garbage-collected 

        # and closed before we are removed ourself 

        self.__orig_socket = socket 

 

    def read_fd(self): 

        """ 

        Read the file descriptor from the socket. 

        """ 

        return recv_fd(self.fileno()) 

 

# FIXME: Any idea how to test this? Starting an external process doesn't sound 

# OK 

class Creator(Parser): 

    """ 

    This starts the socket creator and allows asking for the sockets. 

 

    Note: __process shouldn't be reset once created.  See the note 

    of the SockCreator class for details. 

    """ 

    def __init__(self, path): 

        (local, remote) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM) 

        # Popen does not like, for some reason, having the same socket for 

        # stdin as well as stdout, so we dup it before passing it there. 

        remote2 = socket.fromfd(remote.fileno(), socket.AF_UNIX, 

                                socket.SOCK_STREAM) 

        env = copy.deepcopy(os.environ) 

        env['PATH'] = path 

        self.__process = subprocess.Popen(['b10-sockcreator'], env=env, 

                                          stdin=remote.fileno(), 

                                          stdout=remote2.fileno(), 

                                          preexec_fn=self.__preexec_work) 

        remote.close() 

        remote2.close() 

        Parser.__init__(self, WrappedSocket(local)) 

 

    def __preexec_work(self): 

        """Function used before running a program that needs to run as a 

        different user.""" 

        # Put us into a separate process group so we don't get 

        # SIGINT signals on Ctrl-C (the boss will shut everthing down by 

        # other means). 

        os.setpgrp() 

 

    def pid(self): 

        return self.__process.pid 

 

    def kill(self): 

        logger.warn(BIND10_SOCKCREATOR_KILL) 

        if self.__process is not None: 

            self.__process.kill()