PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Connection/Connection.py

https://gitlab.com/sbDevGit/ZeroNet
Python | 450 lines | 414 code | 26 blank | 10 comment | 24 complexity | 6ab16887b2a0cd050706717ffaf267c9 MD5 | raw file
  1. import socket
  2. import time
  3. import gevent
  4. import msgpack
  5. from Config import config
  6. from Debug import Debug
  7. from util import StreamingMsgpack
  8. from Crypt import CryptConnection
  9. class Connection(object):
  10. __slots__ = (
  11. "sock", "sock_wrapped", "ip", "port", "cert_pin", "site_lock", "id", "protocol", "type", "server", "unpacker", "req_id",
  12. "handshake", "crypt", "connected", "event_connected", "closed", "start_time", "last_recv_time",
  13. "last_message_time", "last_send_time", "last_sent_time", "incomplete_buff_recv", "bytes_recv", "bytes_sent",
  14. "last_ping_delay", "last_req_time", "last_cmd", "bad_actions", "sites", "name", "updateName", "waiting_requests", "waiting_streams"
  15. )
  16. def __init__(self, server, ip, port, sock=None, site_lock=None):
  17. self.sock = sock
  18. self.ip = ip
  19. self.port = port
  20. self.cert_pin = None
  21. if "#" in ip:
  22. self.ip, self.cert_pin = ip.split("#")
  23. self.site_lock = site_lock # Only this site requests allowed (for Tor)
  24. self.id = server.last_connection_id
  25. server.last_connection_id += 1
  26. self.protocol = "?"
  27. self.type = "?"
  28. self.server = server
  29. self.unpacker = None # Stream incoming socket messages here
  30. self.req_id = 0 # Last request id
  31. self.handshake = {} # Handshake info got from peer
  32. self.crypt = None # Connection encryption method
  33. self.sock_wrapped = False # Socket wrapped to encryption
  34. self.connected = False
  35. self.event_connected = gevent.event.AsyncResult() # Solves on handshake received
  36. self.closed = False
  37. # Stats
  38. self.start_time = time.time()
  39. self.last_recv_time = 0
  40. self.last_message_time = 0
  41. self.last_send_time = 0
  42. self.last_sent_time = 0
  43. self.incomplete_buff_recv = 0
  44. self.bytes_recv = 0
  45. self.bytes_sent = 0
  46. self.last_ping_delay = None
  47. self.last_req_time = 0
  48. self.last_cmd = None
  49. self.bad_actions = 0
  50. self.sites = 0
  51. self.name = None
  52. self.updateName()
  53. self.waiting_requests = {} # Waiting sent requests
  54. self.waiting_streams = {} # Waiting response file streams
  55. def updateName(self):
  56. self.name = "Conn#%2s %-12s [%s]" % (self.id, self.ip, self.protocol)
  57. def __str__(self):
  58. return self.name
  59. def __repr__(self):
  60. return "<%s>" % self.__str__()
  61. def log(self, text):
  62. self.server.log.debug("%s > %s" % (self.name, text))
  63. def badAction(self, weight=1):
  64. self.bad_actions += weight
  65. def goodAction(self):
  66. self.bad_actions = 0
  67. # Open connection to peer and wait for handshake
  68. def connect(self):
  69. self.log("Connecting...")
  70. self.type = "out"
  71. if self.ip.endswith(".onion"):
  72. if not self.server.tor_manager or not self.server.tor_manager.enabled:
  73. raise Exception("Can't connect to onion addresses, no Tor controller present")
  74. self.sock = self.server.tor_manager.createSocket(self.ip, self.port)
  75. else:
  76. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  77. self.sock.connect((self.ip, int(self.port)))
  78. # Implicit SSL
  79. if self.cert_pin:
  80. self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa", cert_pin=self.cert_pin)
  81. self.sock.do_handshake()
  82. self.crypt = "tls-rsa"
  83. self.sock_wrapped = True
  84. # Detect protocol
  85. self.send({"cmd": "handshake", "req_id": 0, "params": self.getHandshakeInfo()})
  86. event_connected = self.event_connected
  87. gevent.spawn(self.messageLoop)
  88. return event_connected.get() # Wait for handshake
  89. # Handle incoming connection
  90. def handleIncomingConnection(self, sock):
  91. self.log("Incoming connection...")
  92. self.type = "in"
  93. if self.ip != "127.0.0.1": # Clearnet: Check implicit SSL
  94. try:
  95. if sock.recv(1, gevent.socket.MSG_PEEK) == "\x16":
  96. self.log("Crypt in connection using implicit SSL")
  97. self.sock = CryptConnection.manager.wrapSocket(self.sock, "tls-rsa", True)
  98. self.sock_wrapped = True
  99. self.crypt = "tls-rsa"
  100. except Exception, err:
  101. self.log("Socket peek error: %s" % Debug.formatException(err))
  102. self.messageLoop()
  103. # Message loop for connection
  104. def messageLoop(self):
  105. if not self.sock:
  106. self.log("Socket error: No socket found")
  107. return False
  108. self.protocol = "v2"
  109. self.updateName()
  110. self.connected = True
  111. buff_len = 0
  112. self.unpacker = msgpack.Unpacker()
  113. try:
  114. while True:
  115. buff = self.sock.recv(16 * 1024)
  116. if not buff:
  117. break # Connection closed
  118. buff_len = len(buff)
  119. # Statistics
  120. self.last_recv_time = time.time()
  121. self.incomplete_buff_recv += 1
  122. self.bytes_recv += buff_len
  123. self.server.bytes_recv += buff_len
  124. if not self.unpacker:
  125. self.unpacker = msgpack.Unpacker()
  126. self.unpacker.feed(buff)
  127. buff = None
  128. for message in self.unpacker:
  129. self.incomplete_buff_recv = 0
  130. if "stream_bytes" in message:
  131. self.handleStream(message)
  132. else:
  133. self.handleMessage(message)
  134. message = None
  135. except Exception, err:
  136. if not self.closed:
  137. self.log("Socket error: %s" % Debug.formatException(err))
  138. self.close() # MessageLoop ended, close connection
  139. # My handshake info
  140. def getHandshakeInfo(self):
  141. # No TLS for onion connections
  142. if self.ip.endswith(".onion"):
  143. crypt_supported = []
  144. else:
  145. crypt_supported = CryptConnection.manager.crypt_supported
  146. # No peer id for onion connections
  147. if self.ip.endswith(".onion") or self.ip == "127.0.0.1":
  148. peer_id = ""
  149. else:
  150. peer_id = self.server.peer_id
  151. # Setup peer lock from requested onion address
  152. if self.handshake and self.handshake.get("target_ip", "").endswith(".onion"):
  153. target_onion = self.handshake.get("target_ip").replace(".onion", "") # My onion address
  154. onion_sites = {v: k for k, v in self.server.tor_manager.site_onions.items()} # Inverse, Onion: Site address
  155. self.site_lock = onion_sites.get(target_onion)
  156. if not self.site_lock:
  157. self.server.log.error("Unknown target onion address: %s" % target_onion)
  158. self.site_lock = "unknown"
  159. handshake = {
  160. "version": config.version,
  161. "protocol": "v2",
  162. "peer_id": peer_id,
  163. "fileserver_port": self.server.port,
  164. "port_opened": self.server.port_opened,
  165. "target_ip": self.ip,
  166. "rev": config.rev,
  167. "crypt_supported": crypt_supported,
  168. "crypt": self.crypt
  169. }
  170. if self.site_lock:
  171. handshake["onion"] = self.server.tor_manager.getOnion(self.site_lock)
  172. elif self.ip.endswith(".onion"):
  173. handshake["onion"] = self.server.tor_manager.getOnion("global")
  174. return handshake
  175. def setHandshake(self, handshake):
  176. self.handshake = handshake
  177. if handshake.get("port_opened", None) is False and "onion" not in handshake: # Not connectable
  178. self.port = 0
  179. else:
  180. self.port = handshake["fileserver_port"] # Set peer fileserver port
  181. if handshake.get("onion") and not self.ip.endswith(".onion"): # Set incoming connection's onion address
  182. self.ip = handshake["onion"] + ".onion"
  183. self.updateName()
  184. # Check if we can encrypt the connection
  185. if handshake.get("crypt_supported") and handshake["peer_id"] not in self.server.broken_ssl_peer_ids:
  186. if self.ip.endswith(".onion"):
  187. crypt = None
  188. elif handshake.get("crypt"): # Recommended crypt by server
  189. crypt = handshake["crypt"]
  190. else: # Select the best supported on both sides
  191. crypt = CryptConnection.manager.selectCrypt(handshake["crypt_supported"])
  192. if crypt:
  193. self.crypt = crypt
  194. self.event_connected.set(True) # Mark handshake as done
  195. self.event_connected = None
  196. # Handle incoming message
  197. def handleMessage(self, message):
  198. self.last_message_time = time.time()
  199. if message.get("cmd") == "response": # New style response
  200. if message["to"] in self.waiting_requests:
  201. if self.last_send_time:
  202. ping = time.time() - self.last_send_time
  203. self.last_ping_delay = ping
  204. self.waiting_requests[message["to"]].set(message) # Set the response to event
  205. del self.waiting_requests[message["to"]]
  206. elif message["to"] == 0: # Other peers handshake
  207. ping = time.time() - self.start_time
  208. if config.debug_socket:
  209. self.log("Handshake response: %s, ping: %s" % (message, ping))
  210. self.last_ping_delay = ping
  211. # Server switched to crypt, lets do it also if not crypted already
  212. if message.get("crypt") and not self.sock_wrapped:
  213. self.crypt = message["crypt"]
  214. server = (self.type == "in")
  215. self.log("Crypt out connection using: %s (server side: %s)..." % (self.crypt, server))
  216. self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)
  217. self.sock.do_handshake()
  218. self.sock_wrapped = True
  219. if not self.sock_wrapped and self.cert_pin:
  220. self.log("Crypt connection error: Socket not encrypted, but certificate pin present")
  221. self.close()
  222. return
  223. self.setHandshake(message)
  224. else:
  225. self.log("Unknown response: %s" % message)
  226. elif message.get("cmd"): # Handhsake request
  227. if message["cmd"] == "handshake":
  228. self.handleHandshake(message)
  229. else:
  230. self.server.handleRequest(self, message)
  231. else: # Old style response, no req_id defined
  232. if config.debug_socket:
  233. self.log("Unknown message: %s, waiting: %s" % (message, self.waiting_requests.keys()))
  234. if self.waiting_requests:
  235. last_req_id = min(self.waiting_requests.keys()) # Get the oldest waiting request and set it true
  236. self.waiting_requests[last_req_id].set(message)
  237. del self.waiting_requests[last_req_id] # Remove from waiting request
  238. # Incoming handshake set request
  239. def handleHandshake(self, message):
  240. if config.debug_socket:
  241. self.log("Handshake request: %s" % message)
  242. self.setHandshake(message["params"])
  243. data = self.getHandshakeInfo()
  244. data["cmd"] = "response"
  245. data["to"] = message["req_id"]
  246. self.send(data) # Send response to handshake
  247. # Sent crypt request to client
  248. if self.crypt and not self.sock_wrapped:
  249. server = (self.type == "in")
  250. self.log("Crypt in connection using: %s (server side: %s)..." % (self.crypt, server))
  251. try:
  252. self.sock = CryptConnection.manager.wrapSocket(self.sock, self.crypt, server, cert_pin=self.cert_pin)
  253. self.sock_wrapped = True
  254. except Exception, err:
  255. self.log("Crypt connection error: %s, adding peerid %s as broken ssl." % (err, message["params"]["peer_id"]))
  256. self.server.broken_ssl_peer_ids[message["params"]["peer_id"]] = True
  257. if not self.sock_wrapped and self.cert_pin:
  258. self.log("Crypt connection error: Socket not encrypted, but certificate pin present")
  259. self.close()
  260. # Stream socket directly to a file
  261. def handleStream(self, message):
  262. read_bytes = message["stream_bytes"] # Bytes left we have to read from socket
  263. try:
  264. buff = self.unpacker.read_bytes(min(16 * 1024, read_bytes)) # Check if the unpacker has something left in buffer
  265. except Exception, err:
  266. buff = ""
  267. file = self.waiting_streams[message["to"]]
  268. if buff:
  269. read_bytes -= len(buff)
  270. file.write(buff)
  271. if config.debug_socket:
  272. self.log("Starting stream %s: %s bytes (%s from unpacker)" % (message["to"], message["stream_bytes"], len(buff)))
  273. try:
  274. while 1:
  275. if read_bytes <= 0:
  276. break
  277. buff = self.sock.recv(16 * 1024)
  278. if not buff:
  279. break
  280. buff_len = len(buff)
  281. read_bytes -= buff_len
  282. file.write(buff)
  283. # Statistics
  284. self.last_recv_time = time.time()
  285. self.incomplete_buff_recv += 1
  286. self.bytes_recv += buff_len
  287. self.server.bytes_recv += buff_len
  288. except Exception, err:
  289. self.log("Stream read error: %s" % Debug.formatException(err))
  290. if config.debug_socket:
  291. self.log("End stream %s" % message["to"])
  292. self.incomplete_buff_recv = 0
  293. self.waiting_requests[message["to"]].set(message) # Set the response to event
  294. del self.waiting_streams[message["to"]]
  295. del self.waiting_requests[message["to"]]
  296. # Send data to connection
  297. def send(self, message, streaming=False):
  298. if config.debug_socket:
  299. self.log("Send: %s, to: %s, streaming: %s, site: %s, inner_path: %s, req_id: %s" % (
  300. message.get("cmd"), message.get("to"), streaming,
  301. message.get("params", {}).get("site"), message.get("params", {}).get("inner_path"),
  302. message.get("req_id"))
  303. )
  304. self.last_send_time = time.time()
  305. try:
  306. if streaming:
  307. bytes_sent = StreamingMsgpack.stream(message, self.sock.sendall)
  308. message = None
  309. self.bytes_sent += bytes_sent
  310. self.server.bytes_sent += bytes_sent
  311. else:
  312. data = msgpack.packb(message)
  313. message = None
  314. self.bytes_sent += len(data)
  315. self.server.bytes_sent += len(data)
  316. self.sock.sendall(data)
  317. except Exception, err:
  318. self.log("Send errror: %s" % Debug.formatException(err))
  319. self.close()
  320. return False
  321. self.last_sent_time = time.time()
  322. return True
  323. # Stream raw file to connection
  324. def sendRawfile(self, file, read_bytes):
  325. buff = 64 * 1024
  326. bytes_left = read_bytes
  327. while True:
  328. self.last_send_time = time.time()
  329. self.sock.sendall(
  330. file.read(min(bytes_left, buff))
  331. )
  332. bytes_left -= buff
  333. if bytes_left <= 0:
  334. break
  335. self.bytes_sent += read_bytes
  336. self.server.bytes_sent += read_bytes
  337. return True
  338. # Create and send a request to peer
  339. def request(self, cmd, params={}, stream_to=None):
  340. # Last command sent more than 10 sec ago, timeout
  341. if self.waiting_requests and self.protocol == "v2" and time.time() - max(self.last_req_time, self.last_recv_time) > 10:
  342. self.log("Request %s timeout: %s" % (self.last_cmd, time.time() - self.last_send_time))
  343. self.close()
  344. return False
  345. self.last_req_time = time.time()
  346. self.last_cmd = cmd
  347. self.req_id += 1
  348. data = {"cmd": cmd, "req_id": self.req_id, "params": params}
  349. event = gevent.event.AsyncResult() # Create new event for response
  350. self.waiting_requests[self.req_id] = event
  351. if stream_to:
  352. self.waiting_streams[self.req_id] = stream_to
  353. self.send(data) # Send request
  354. res = event.get() # Wait until event solves
  355. return res
  356. def ping(self):
  357. s = time.time()
  358. response = None
  359. with gevent.Timeout(10.0, False):
  360. try:
  361. response = self.request("ping")
  362. except Exception, err:
  363. self.log("Ping error: %s" % Debug.formatException(err))
  364. if response and "body" in response and response["body"] == "Pong!":
  365. self.last_ping_delay = time.time() - s
  366. return True
  367. else:
  368. return False
  369. # Close connection
  370. def close(self):
  371. if self.closed:
  372. return False # Already closed
  373. self.closed = True
  374. self.connected = False
  375. if self.event_connected:
  376. self.event_connected.set(False)
  377. if config.debug_socket:
  378. self.log(
  379. "Closing connection, waiting_requests: %s, buff: %s..." %
  380. (len(self.waiting_requests), self.incomplete_buff_recv)
  381. )
  382. for request in self.waiting_requests.values(): # Mark pending requests failed
  383. request.set(False)
  384. self.waiting_requests = {}
  385. self.waiting_streams = {}
  386. self.sites = 0
  387. self.server.removeConnection(self) # Remove connection from server registry
  388. try:
  389. if self.sock:
  390. self.sock.shutdown(gevent.socket.SHUT_WR)
  391. self.sock.close()
  392. except Exception, err:
  393. if config.debug_socket:
  394. self.log("Close error: %s" % err)
  395. # Little cleanup
  396. self.sock = None
  397. self.unpacker = None
  398. self.event_connected = None