Hide keyboard shortcuts

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# This file is part of Xpra. 

2# Copyright (C) 2020 Antoine Martin <antoine@xpra.org> 

3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

4# later version. See the file COPYING for details. 

5#pylint: disable-msg=E1101 

6 

7import os 

8import sys 

9 

10from xpra import __version__ as VERSION 

11from xpra.util import envint, envfloat, typedict, DETACH_REQUEST, PROTOCOL_ERROR 

12from xpra.os_util import bytestostr, get_machine_id 

13from xpra.net.bytestreams import log_new_connection 

14from xpra.net.socket_util import create_sockets, add_listen_socket, accept_connection, setup_local_sockets 

15from xpra.net.net_util import get_network_caps 

16from xpra.net.protocol import Protocol 

17from xpra.exit_codes import EXIT_OK, EXIT_FAILURE 

18from xpra.client.mixins.stub_client_mixin import StubClientMixin 

19from xpra.scripts.config import InitException 

20from xpra.log import Logger 

21 

22log = Logger("network") 

23 

24SOCKET_TIMEOUT = envfloat("XPRA_CLIENT_SOCKET_TIMEOUT", "0.1") 

25MAX_CONCURRENT_CONNECTIONS = envint("XPRA_MAX_CONCURRENT_CONNECTIONS", 5) 

26REQUEST_TIMEOUT = envint("XPRA_CLIENT_REQUEST_TIMEOUT", 10) 

27 

28 

29""" 

30Mixin for adding listening sockets to the client, 

31those can be used for 

32- requesting disconnection 

33- info request 

34""" 

35class NetworkListener(StubClientMixin): 

36 

37 def __init__(self): 

38 self.sockets = {} 

39 self.socket_info = {} 

40 self.socket_options = {} 

41 self.socket_cleanup = [] 

42 self._potential_protocols = [] 

43 self._close_timers = {} 

44 

45 

46 def init(self, opts): 

47 def err(msg): 

48 raise InitException(msg) 

49 #don't listen on udp sockets for now: 

50 opts.bind_udp = () 

51 self.sockets = create_sockets(opts, err) 

52 if opts.bind: 

53 local_sockets = setup_local_sockets(opts.bind, 

54 None, opts.client_socket_dirs, 

55 str(os.getpid()), True, 

56 opts.mmap_group, opts.socket_permissions) 

57 self.sockets.update(local_sockets) 

58 

59 def run(self): 

60 self.start_listen_sockets() 

61 

62 def cleanup(self): 

63 self.cleanup_sockets() 

64 

65 

66 def cleanup_sockets(self): 

67 ct = dict(self._close_timers) 

68 self._close_timers = {} 

69 for proto, tid in ct.items(): 

70 self.source_remove(tid) 

71 proto.close() 

72 socket_cleanup = self.socket_cleanup 

73 log("cleanup_sockets() socket_cleanup=%s", socket_cleanup) 

74 self.socket_cleanup = [] 

75 for c in socket_cleanup: 

76 try: 

77 c() 

78 except Exception: 

79 log.error("Error during socket listener cleanup", exc_info=True) 

80 sockets = self.sockets 

81 self.sockets = {} 

82 log("cleanup_sockets() sockets=%s", sockets) 

83 for sdef in sockets.keys(): 

84 c = sdef[-1] 

85 try: 

86 c() 

87 except Exception: 

88 log.error("Error during socket cleanup", exc_info=True) 

89 

90 

91 def start_listen_sockets(self): 

92 for sock_def, options in self.sockets.items(): 

93 socktype, sock, info, _ = sock_def 

94 log("start_listen_sockets() will add %s socket %s (%s)", socktype, sock, info) 

95 self.socket_info[sock] = info 

96 self.socket_options[sock] = options 

97 self.idle_add(self.add_listen_socket, socktype, sock, options) 

98 

99 def add_listen_socket(self, socktype, sock, options): 

100 info = self.socket_info.get(sock) 

101 log("add_listen_socket(%s, %s, %s) info=%s", socktype, sock, options, info) 

102 cleanup = add_listen_socket(socktype, sock, info, self._new_connection, None, options) 

103 if cleanup: 

104 self.socket_cleanup.append(cleanup) 

105 

106 def _new_connection(self, socktype, listener, handle=0): 

107 """ 

108 Accept the new connection, 

109 verify that there aren't too many, 

110 start a thread to dispatch it to the correct handler. 

111 """ 

112 log("_new_connection%s", (listener, socktype, handle)) 

113 if self.exit_code is not None: 

114 log("ignoring new connection during shutdown") 

115 return False 

116 try: 

117 self.handle_new_connection(socktype, listener, handle) 

118 except Exception: 

119 log.error("Error handling new connection", exc_info=True) 

120 return self.exit_code is None 

121 

122 def handle_new_connection(self, socktype, listener, handle): 

123 assert socktype, "cannot find socket type for %s" % listener 

124 socket_options = self.socket_options.get(listener, {}) 

125 if socktype=="named-pipe": 

126 from xpra.platform.win32.namedpipes.connection import NamedPipeConnection 

127 conn = NamedPipeConnection(listener.pipe_name, handle, socket_options) 

128 log.info("New %s connection received on %s", socktype, conn.target) 

129 self.make_protocol(socktype, conn, listener) 

130 return 

131 conn = accept_connection(socktype, listener, SOCKET_TIMEOUT, socket_options) 

132 if conn is None: 

133 return 

134 #limit number of concurrent network connections: 

135 if len(self._potential_protocols)>=MAX_CONCURRENT_CONNECTIONS: 

136 log.error("Error: too many connections (%i)", len(self._potential_protocols)) 

137 log.error(" ignoring new one: %s", conn.endpoint or conn) 

138 conn.close() 

139 return 

140 try: 

141 sockname = conn._socket.getsockname() 

142 except Exception: 

143 sockname = "" 

144 log("handle_new_connection%s sockname=%s", 

145 (socktype, listener, handle), sockname) 

146 socket_info = self.socket_info.get(listener) 

147 log_new_connection(conn, socket_info) 

148 self.make_protocol(socktype, conn, listener) 

149 

150 def make_protocol(self, socktype, conn, listener): 

151 socktype = socktype.lower() 

152 protocol = Protocol(self, conn, self.process_network_packet) 

153 #protocol.large_packets.append(b"info-response") 

154 protocol.socket_type = socktype 

155 self._potential_protocols.append(protocol) 

156 protocol.authenticators = () 

157 protocol.start() 

158 #self.schedule_verify_connection_accepted(protocol, self._accept_timeout) 

159 

160 def process_network_packet(self, proto, packet): 

161 log("process_network_packet: %s", packet) 

162 packet_type = bytestostr(packet[0]) 

163 def close(): 

164 t = self._close_timers.pop(proto, None) 

165 if t: 

166 proto.close() 

167 try: 

168 self._potential_protocols.remove(proto) 

169 except ValueError: 

170 pass 

171 def hello_reply(data): 

172 proto.send_now(["hello", data]) 

173 if packet_type=="hello": 

174 caps = typedict(packet[1]) 

175 proto.parse_remote_caps(caps) 

176 proto.enable_compressor_from_caps(caps) 

177 proto.enable_encoder_from_caps(caps) 

178 request = caps.strget("request") 

179 if request=="info": 

180 def send_info(): 

181 info = self.get_info() 

182 info["network"] = get_network_caps() 

183 hello_reply(info) 

184 #run in UI thread: 

185 self.idle_add(send_info) 

186 elif request=="id": 

187 hello_reply(self.get_id_info()) 

188 elif request=="detach": 

189 def protocol_closed(): 

190 self.disconnect_and_quit(EXIT_OK, "network request") 

191 proto.send_disconnect([DETACH_REQUEST], done_callback=protocol_closed) 

192 return 

193 elif request=="version": 

194 hello_reply({"version" : VERSION}) 

195 elif request=="command": 

196 command = caps.strtupleget("command_request") 

197 log("command request: %s", command) 

198 def process_control(): 

199 try: 

200 self._process_control(["control"]+list(command)) 

201 code = EXIT_OK 

202 response = "done" 

203 except Exception as e: 

204 code = EXIT_FAILURE 

205 response = str(e) 

206 hello_reply({"command_response" : (code, response)}) 

207 self.idle_add(process_control) 

208 else: 

209 log.info("request '%s' is not handled by this client", request) 

210 proto.send_disconnect([PROTOCOL_ERROR]) 

211 elif packet_type in (Protocol.CONNECTION_LOST, Protocol.GIBBERISH): 

212 close() 

213 return 

214 else: 

215 log.info("packet '%s' is not handled by this client", packet_type) 

216 proto.send_disconnect([PROTOCOL_ERROR]) 

217 #make sure the connection is closed: 

218 tid = self.timeout_add(REQUEST_TIMEOUT*1000, close) 

219 self._close_timers[proto] = tid 

220 

221 def get_id_info(self) -> dict: 

222 #minimal information for identifying the session 

223 return { 

224 "session-type" : "client", 

225 "session-name" : self.session_name, 

226 "platform" : sys.platform, 

227 "pid" : os.getpid(), 

228 "machine-id" : get_machine_id(), 

229 }