Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tornado/netutil.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

236 statements  

1# 

2# Copyright 2011 Facebook 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16"""Miscellaneous network utility code.""" 

17 

18import asyncio 

19import concurrent.futures 

20import errno 

21import functools 

22import os 

23import socket 

24import ssl 

25import stat 

26import sys 

27from collections.abc import Awaitable, Callable 

28from typing import Any 

29 

30from tornado.concurrent import dummy_executor, run_on_executor 

31from tornado.ioloop import IOLoop 

32from tornado.util import Configurable, errno_from_exception 

33 

34 

35# Note that the naming of ssl.Purpose is confusing; the purpose 

36# of a context is to authenticate the opposite side of the connection. 

37# 

38# These are created lazily (rather than at import time) because 

39# ssl.create_default_context() loads the platform's default certificate 

40# store, which on some platforms (e.g. macOS) may start background 

41# threads. Creating these contexts eagerly at import time could leave 

42# such threads running at a call to os.fork() (e.g. in 

43# tornado.process.fork_processes), which is unsafe. 

44@functools.lru_cache(maxsize=1) 

45def _client_ssl_defaults() -> ssl.SSLContext: 

46 ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) 

47 if hasattr(ssl, "OP_NO_COMPRESSION"): 

48 # See netutil.ssl_options_to_context 

49 ctx.options |= ssl.OP_NO_COMPRESSION 

50 return ctx 

51 

52 

53@functools.lru_cache(maxsize=1) 

54def _server_ssl_defaults() -> ssl.SSLContext: 

55 ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) 

56 if hasattr(ssl, "OP_NO_COMPRESSION"): 

57 ctx.options |= ssl.OP_NO_COMPRESSION 

58 return ctx 

59 

60 

61# ThreadedResolver runs getaddrinfo on a thread. If the hostname is unicode, 

62# getaddrinfo attempts to import encodings.idna. If this is done at 

63# module-import time, the import lock is already held by the main thread, 

64# leading to deadlock. Avoid it by caching the idna encoder on the main 

65# thread now. 

66"foo".encode("idna") 

67 

68# For undiagnosed reasons, 'latin1' codec may also need to be preloaded. 

69"foo".encode("latin1") 

70 

71# Default backlog used when calling sock.listen() 

72_DEFAULT_BACKLOG = 128 

73 

74 

75def bind_sockets( 

76 port: int, 

77 address: str | None = None, 

78 family: socket.AddressFamily = socket.AF_UNSPEC, 

79 backlog: int = _DEFAULT_BACKLOG, 

80 flags: int | None = None, 

81 reuse_port: bool = False, 

82) -> list[socket.socket]: 

83 """Creates listening sockets bound to the given port and address. 

84 

85 Returns a list of socket objects (multiple sockets are returned if 

86 the given address maps to multiple IP addresses, which is most common 

87 for mixed IPv4 and IPv6 use). 

88 

89 Address may be either an IP address or hostname. If it's a hostname, 

90 the server will listen on all IP addresses associated with the 

91 name. Address may be an empty string or None to listen on all 

92 available interfaces. Family may be set to either `socket.AF_INET` 

93 or `socket.AF_INET6` to restrict to IPv4 or IPv6 addresses, otherwise 

94 both will be used if available. 

95 

96 The ``backlog`` argument has the same meaning as for 

97 `socket.listen() <socket.socket.listen>`. 

98 

99 ``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like 

100 ``socket.AI_PASSIVE | socket.AI_NUMERICHOST``. 

101 

102 ``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket 

103 in the list. If your platform doesn't support this option ValueError will 

104 be raised. 

105 """ 

106 if reuse_port and not hasattr(socket, "SO_REUSEPORT"): 

107 raise ValueError("the platform doesn't support SO_REUSEPORT") 

108 

109 sockets = [] 

110 if address == "": 

111 address = None 

112 if not socket.has_ipv6 and family == socket.AF_UNSPEC: 

113 # Python can be compiled with --disable-ipv6, which causes 

114 # operations on AF_INET6 sockets to fail, but does not 

115 # automatically exclude those results from getaddrinfo 

116 # results. 

117 # http://bugs.python.org/issue16208 

118 family = socket.AF_INET 

119 if flags is None: 

120 flags = socket.AI_PASSIVE 

121 bound_port = None 

122 unique_addresses: set = set() 

123 for res in sorted( 

124 socket.getaddrinfo(address, port, family, socket.SOCK_STREAM, 0, flags), 

125 key=lambda x: x[0], 

126 ): 

127 if res in unique_addresses: 

128 continue 

129 

130 unique_addresses.add(res) 

131 

132 af, socktype, proto, canonname, sockaddr = res 

133 if ( 

134 sys.platform == "darwin" 

135 and address == "localhost" 

136 and af == socket.AF_INET6 

137 and sockaddr[3] != 0 # type: ignore 

138 ): 

139 # Mac OS X includes a link-local address fe80::1%lo0 in the 

140 # getaddrinfo results for 'localhost'. However, the firewall 

141 # doesn't understand that this is a local address and will 

142 # prompt for access (often repeatedly, due to an apparent 

143 # bug in its ability to remember granting access to an 

144 # application). Skip these addresses. 

145 continue 

146 try: 

147 sock = socket.socket(af, socktype, proto) 

148 except OSError as e: 

149 if errno_from_exception(e) == errno.EAFNOSUPPORT: 

150 continue 

151 raise 

152 if os.name != "nt": 

153 try: 

154 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 

155 except OSError as e: 

156 if errno_from_exception(e) != errno.ENOPROTOOPT: 

157 # Hurd doesn't support SO_REUSEADDR. 

158 raise 

159 if reuse_port: 

160 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 

161 if af == socket.AF_INET6: 

162 # On linux, ipv6 sockets accept ipv4 too by default, 

163 # but this makes it impossible to bind to both 

164 # 0.0.0.0 in ipv4 and :: in ipv6. On other systems, 

165 # separate sockets *must* be used to listen for both ipv4 

166 # and ipv6. For consistency, always disable ipv4 on our 

167 # ipv6 sockets and use a separate ipv4 socket when needed. 

168 # 

169 # Python 2.x on windows doesn't have IPPROTO_IPV6. 

170 if hasattr(socket, "IPPROTO_IPV6"): 

171 sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) 

172 

173 # automatic port allocation with port=None 

174 # should bind on the same port on IPv4 and IPv6 

175 host, requested_port = sockaddr[:2] 

176 if requested_port == 0 and bound_port is not None: 

177 sockaddr = tuple([host, bound_port] + list(sockaddr[2:])) 

178 

179 sock.setblocking(False) 

180 try: 

181 sock.bind(sockaddr) 

182 except OSError as e: 

183 if ( 

184 errno_from_exception(e) == errno.EADDRNOTAVAIL 

185 and address == "localhost" 

186 and sockaddr[0] == "::1" 

187 ): 

188 # On some systems (most notably docker with default 

189 # configurations), ipv6 is partially disabled: 

190 # socket.has_ipv6 is true, we can create AF_INET6 

191 # sockets, and getaddrinfo("localhost", ..., 

192 # AF_PASSIVE) resolves to ::1, but we get an error 

193 # when binding. 

194 # 

195 # Swallow the error, but only for this specific case. 

196 # If EADDRNOTAVAIL occurs in other situations, it 

197 # might be a real problem like a typo in a 

198 # configuration. 

199 sock.close() 

200 continue 

201 else: 

202 raise 

203 bound_port = sock.getsockname()[1] 

204 sock.listen(backlog) 

205 sockets.append(sock) 

206 return sockets 

207 

208 

209if hasattr(socket, "AF_UNIX"): 

210 

211 def bind_unix_socket( 

212 file: str, mode: int = 0o600, backlog: int = _DEFAULT_BACKLOG 

213 ) -> socket.socket: 

214 """Creates a listening unix socket. 

215 

216 If a socket with the given name already exists, it will be deleted. 

217 If any other file with that name exists, an exception will be 

218 raised. 

219 

220 Returns a socket object (not a list of socket objects like 

221 `bind_sockets`) 

222 """ 

223 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 

224 try: 

225 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 

226 except OSError as e: 

227 if errno_from_exception(e) != errno.ENOPROTOOPT: 

228 # Hurd doesn't support SO_REUSEADDR 

229 raise 

230 sock.setblocking(False) 

231 # File names comprising of an initial null-byte denote an abstract 

232 # namespace, on Linux, and therefore are not subject to file system 

233 # orientated processing. 

234 if not file.startswith("\0"): 

235 try: 

236 st = os.stat(file) 

237 except FileNotFoundError: 

238 pass 

239 else: 

240 if stat.S_ISSOCK(st.st_mode): 

241 os.remove(file) 

242 else: 

243 raise ValueError("File %s exists and is not a socket" % file) 

244 sock.bind(file) 

245 os.chmod(file, mode) 

246 else: 

247 sock.bind(file) 

248 sock.listen(backlog) 

249 return sock 

250 

251 

252def add_accept_handler( 

253 sock: socket.socket, callback: Callable[[socket.socket, Any], None] 

254) -> Callable[[], None]: 

255 """Adds an `.IOLoop` event handler to accept new connections on ``sock``. 

256 

257 When a connection is accepted, ``callback(connection, address)`` will 

258 be run (``connection`` is a socket object, and ``address`` is the 

259 address of the other end of the connection). Note that this signature 

260 is different from the ``callback(fd, events)`` signature used for 

261 `.IOLoop` handlers. 

262 

263 A callable is returned which, when called, will remove the `.IOLoop` 

264 event handler and stop processing further incoming connections. 

265 

266 .. versionchanged:: 5.0 

267 The ``io_loop`` argument (deprecated since version 4.1) has been removed. 

268 

269 .. versionchanged:: 5.0 

270 A callable is returned (``None`` was returned before). 

271 """ 

272 io_loop = IOLoop.current() 

273 removed = [False] 

274 

275 def accept_handler(fd: socket.socket, events: int) -> None: 

276 # More connections may come in while we're handling callbacks; 

277 # to prevent starvation of other tasks we must limit the number 

278 # of connections we accept at a time. Ideally we would accept 

279 # up to the number of connections that were waiting when we 

280 # entered this method, but this information is not available 

281 # (and rearranging this method to call accept() as many times 

282 # as possible before running any callbacks would have adverse 

283 # effects on load balancing in multiprocess configurations). 

284 # Instead, we use the (default) listen backlog as a rough 

285 # heuristic for the number of connections we can reasonably 

286 # accept at once. 

287 for i in range(_DEFAULT_BACKLOG): 

288 if removed[0]: 

289 # The socket was probably closed 

290 return 

291 try: 

292 connection, address = sock.accept() 

293 except BlockingIOError: 

294 # EWOULDBLOCK indicates we have accepted every 

295 # connection that is available. 

296 return 

297 except ConnectionAbortedError: 

298 # ECONNABORTED indicates that there was a connection 

299 # but it was closed while still in the accept queue. 

300 # (observed on FreeBSD). 

301 continue 

302 callback(connection, address) 

303 

304 def remove_handler() -> None: 

305 io_loop.remove_handler(sock) 

306 removed[0] = True 

307 

308 io_loop.add_handler(sock, accept_handler, IOLoop.READ) 

309 return remove_handler 

310 

311 

312def is_valid_ip(ip: str) -> bool: 

313 """Returns ``True`` if the given string is a well-formed IP address. 

314 

315 Supports IPv4 and IPv6. 

316 """ 

317 if not ip or "\x00" in ip: 

318 # getaddrinfo resolves empty strings to localhost, and truncates 

319 # on zero bytes. 

320 return False 

321 try: 

322 res = socket.getaddrinfo( 

323 ip, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_NUMERICHOST 

324 ) 

325 return bool(res) 

326 except socket.gaierror as e: 

327 if e.args[0] == socket.EAI_NONAME: 

328 return False 

329 raise 

330 except UnicodeError: 

331 # `socket.getaddrinfo` will raise a UnicodeError from the 

332 # `idna` decoder if the input is longer than 63 characters, 

333 # even for socket.AI_NUMERICHOST. See 

334 # https://bugs.python.org/issue32958 for discussion 

335 return False 

336 return True 

337 

338 

339class Resolver(Configurable): 

340 """Configurable asynchronous DNS resolver interface. 

341 

342 By default, a blocking implementation is used (which simply calls 

343 `socket.getaddrinfo`). An alternative implementation can be 

344 chosen with the `Resolver.configure <.Configurable.configure>` 

345 class method:: 

346 

347 Resolver.configure('tornado.netutil.ThreadedResolver') 

348 

349 The implementations of this interface included with Tornado are 

350 

351 * `tornado.netutil.DefaultLoopResolver` 

352 * `tornado.netutil.DefaultExecutorResolver` (deprecated) 

353 * `tornado.netutil.BlockingResolver` (deprecated) 

354 * `tornado.netutil.ThreadedResolver` (deprecated) 

355 * `tornado.netutil.OverrideResolver` 

356 * `tornado.platform.caresresolver.CaresResolver` (deprecated) 

357 

358 .. versionchanged:: 5.0 

359 The default implementation has changed from `BlockingResolver` to 

360 `DefaultExecutorResolver`. 

361 

362 .. versionchanged:: 6.2 

363 The default implementation has changed from `DefaultExecutorResolver` to 

364 `DefaultLoopResolver`. 

365 """ 

366 

367 @classmethod 

368 def configurable_base(cls) -> type["Resolver"]: 

369 return Resolver 

370 

371 @classmethod 

372 def configurable_default(cls) -> type["Resolver"]: 

373 return DefaultLoopResolver 

374 

375 def resolve( 

376 self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

377 ) -> Awaitable[list[tuple[int, Any]]]: 

378 """Resolves an address. 

379 

380 The ``host`` argument is a string which may be a hostname or a 

381 literal IP address. 

382 

383 Returns a `.Future` whose result is a list of (family, 

384 address) pairs, where address is a tuple suitable to pass to 

385 `socket.connect <socket.socket.connect>` (i.e. a ``(host, 

386 port)`` pair for IPv4; additional fields may be present for 

387 IPv6). If a ``callback`` is passed, it will be run with the 

388 result as an argument when it is complete. 

389 

390 :raises IOError: if the address cannot be resolved. 

391 

392 .. versionchanged:: 4.4 

393 Standardized all implementations to raise `IOError`. 

394 

395 .. versionchanged:: 6.0 The ``callback`` argument was removed. 

396 Use the returned awaitable object instead. 

397 

398 """ 

399 raise NotImplementedError() 

400 

401 def close(self) -> None: 

402 """Closes the `Resolver`, freeing any resources used. 

403 

404 .. versionadded:: 3.1 

405 

406 """ 

407 pass 

408 

409 

410def _resolve_addr( 

411 host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

412) -> list[tuple[int, Any]]: 

413 # On Solaris, getaddrinfo fails if the given port is not found 

414 # in /etc/services and no socket type is given, so we must pass 

415 # one here. The socket type used here doesn't seem to actually 

416 # matter (we discard the one we get back in the results), 

417 # so the addresses we return should still be usable with SOCK_DGRAM. 

418 addrinfo = socket.getaddrinfo(host, port, family, socket.SOCK_STREAM) 

419 results = [] 

420 for fam, socktype, proto, canonname, address in addrinfo: 

421 results.append((fam, address)) 

422 return results # type: ignore 

423 

424 

425class DefaultExecutorResolver(Resolver): 

426 """Resolver implementation using `.IOLoop.run_in_executor`. 

427 

428 .. versionadded:: 5.0 

429 

430 .. deprecated:: 6.2 

431 

432 Use `DefaultLoopResolver` instead. 

433 """ 

434 

435 async def resolve( 

436 self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

437 ) -> list[tuple[int, Any]]: 

438 result = await IOLoop.current().run_in_executor( 

439 None, _resolve_addr, host, port, family 

440 ) 

441 return result 

442 

443 

444class DefaultLoopResolver(Resolver): 

445 """Resolver implementation using `asyncio.loop.getaddrinfo`.""" 

446 

447 async def resolve( 

448 self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

449 ) -> list[tuple[int, Any]]: 

450 # On Solaris, getaddrinfo fails if the given port is not found 

451 # in /etc/services and no socket type is given, so we must pass 

452 # one here. The socket type used here doesn't seem to actually 

453 # matter (we discard the one we get back in the results), 

454 # so the addresses we return should still be usable with SOCK_DGRAM. 

455 return [ 

456 (fam, address) 

457 for fam, _, _, _, address in await asyncio.get_running_loop().getaddrinfo( 

458 host, port, family=family, type=socket.SOCK_STREAM 

459 ) 

460 ] 

461 

462 

463class ExecutorResolver(Resolver): 

464 """Resolver implementation using a `concurrent.futures.Executor`. 

465 

466 Use this instead of `ThreadedResolver` when you require additional 

467 control over the executor being used. 

468 

469 The executor will be shut down when the resolver is closed unless 

470 ``close_resolver=False``; use this if you want to reuse the same 

471 executor elsewhere. 

472 

473 .. versionchanged:: 5.0 

474 The ``io_loop`` argument (deprecated since version 4.1) has been removed. 

475 

476 .. deprecated:: 5.0 

477 The default `Resolver` now uses `asyncio.loop.getaddrinfo`; 

478 use that instead of this class. 

479 """ 

480 

481 def initialize( 

482 self, 

483 executor: concurrent.futures.Executor | None = None, 

484 close_executor: bool = True, 

485 ) -> None: 

486 if executor is not None: 

487 self.executor = executor 

488 self.close_executor = close_executor 

489 else: 

490 self.executor = dummy_executor 

491 self.close_executor = False 

492 

493 def close(self) -> None: 

494 if self.close_executor: 

495 self.executor.shutdown() 

496 self.executor = None # type: ignore 

497 

498 @run_on_executor 

499 def resolve( 

500 self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

501 ) -> list[tuple[int, Any]]: 

502 return _resolve_addr(host, port, family) 

503 

504 

505class BlockingResolver(ExecutorResolver): 

506 """Default `Resolver` implementation, using `socket.getaddrinfo`. 

507 

508 The `.IOLoop` will be blocked during the resolution, although the 

509 callback will not be run until the next `.IOLoop` iteration. 

510 

511 .. deprecated:: 5.0 

512 The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead 

513 of this class. 

514 """ 

515 

516 def initialize(self) -> None: # type: ignore 

517 super().initialize() 

518 

519 

520class ThreadedResolver(ExecutorResolver): 

521 """Multithreaded non-blocking `Resolver` implementation. 

522 

523 The thread pool size can be configured with:: 

524 

525 Resolver.configure('tornado.netutil.ThreadedResolver', 

526 num_threads=10) 

527 

528 .. versionchanged:: 3.1 

529 All ``ThreadedResolvers`` share a single thread pool, whose 

530 size is set by the first one to be created. 

531 

532 .. deprecated:: 5.0 

533 The default `Resolver` now uses `.IOLoop.run_in_executor`; use that instead 

534 of this class. 

535 """ 

536 

537 _threadpool: concurrent.futures.ThreadPoolExecutor | None = None 

538 _threadpool_pid: int | None = None 

539 

540 def initialize(self, num_threads: int = 10) -> None: # type: ignore 

541 threadpool = ThreadedResolver._create_threadpool(num_threads) 

542 super().initialize(executor=threadpool, close_executor=False) 

543 

544 @classmethod 

545 def _create_threadpool( 

546 cls, num_threads: int 

547 ) -> concurrent.futures.ThreadPoolExecutor: 

548 pid = os.getpid() 

549 if cls._threadpool_pid != pid: 

550 # Threads cannot survive after a fork, so if our pid isn't what it 

551 # was when we created the pool then delete it. 

552 cls._threadpool = None 

553 if cls._threadpool is None: 

554 cls._threadpool = concurrent.futures.ThreadPoolExecutor(num_threads) 

555 cls._threadpool_pid = pid 

556 return cls._threadpool 

557 

558 

559class OverrideResolver(Resolver): 

560 """Wraps a resolver with a mapping of overrides. 

561 

562 This can be used to make local DNS changes (e.g. for testing) 

563 without modifying system-wide settings. 

564 

565 The mapping can be in three formats:: 

566 

567 { 

568 # Hostname to host or ip 

569 "example.com": "127.0.1.1", 

570 

571 # Host+port to host+port 

572 ("login.example.com", 443): ("localhost", 1443), 

573 

574 # Host+port+address family to host+port 

575 ("login.example.com", 443, socket.AF_INET6): ("::1", 1443), 

576 } 

577 

578 .. versionchanged:: 5.0 

579 Added support for host-port-family triplets. 

580 """ 

581 

582 def initialize(self, resolver: Resolver, mapping: dict) -> None: 

583 self.resolver = resolver 

584 self.mapping = mapping 

585 

586 def close(self) -> None: 

587 self.resolver.close() 

588 

589 def resolve( 

590 self, host: str, port: int, family: socket.AddressFamily = socket.AF_UNSPEC 

591 ) -> Awaitable[list[tuple[int, Any]]]: 

592 if (host, port, family) in self.mapping: 

593 host, port = self.mapping[(host, port, family)] 

594 elif (host, port) in self.mapping: 

595 host, port = self.mapping[(host, port)] 

596 elif host in self.mapping: 

597 host = self.mapping[host] 

598 return self.resolver.resolve(host, port, family) 

599 

600 

601# These are the keyword arguments to ssl.wrap_socket that must be translated 

602# to their SSLContext equivalents (the other arguments are still passed 

603# to SSLContext.wrap_socket). 

604_SSL_CONTEXT_KEYWORDS = frozenset( 

605 ["ssl_version", "certfile", "keyfile", "cert_reqs", "ca_certs", "ciphers"] 

606) 

607 

608 

609def ssl_options_to_context( 

610 ssl_options: dict[str, Any] | ssl.SSLContext, 

611 server_side: bool | None = None, 

612) -> ssl.SSLContext: 

613 """Try to convert an ``ssl_options`` dictionary to an 

614 `~ssl.SSLContext` object. 

615 

616 The ``ssl_options`` argument may be either an `ssl.SSLContext` object or a dictionary containing 

617 keywords to be passed to ``ssl.SSLContext.wrap_socket``. This function converts the dict form 

618 to its `~ssl.SSLContext` equivalent, and may be used when a component which accepts both forms 

619 needs to upgrade to the `~ssl.SSLContext` version to use features like SNI or ALPN. 

620 

621 .. versionchanged:: 6.2 

622 

623 Added server_side argument. Omitting this argument will result in a DeprecationWarning on 

624 Python 3.10. 

625 

626 """ 

627 if isinstance(ssl_options, ssl.SSLContext): 

628 return ssl_options 

629 assert isinstance(ssl_options, dict) 

630 assert all(k in _SSL_CONTEXT_KEYWORDS for k in ssl_options), ssl_options 

631 # TODO: Now that we have the server_side argument, can we switch to 

632 # create_default_context or would that change behavior? 

633 default_version = ssl.PROTOCOL_TLS 

634 if server_side: 

635 default_version = ssl.PROTOCOL_TLS_SERVER 

636 elif server_side is not None: 

637 default_version = ssl.PROTOCOL_TLS_CLIENT 

638 context = ssl.SSLContext(ssl_options.get("ssl_version", default_version)) 

639 if "certfile" in ssl_options: 

640 context.load_cert_chain( 

641 ssl_options["certfile"], ssl_options.get("keyfile", None) 

642 ) 

643 if "cert_reqs" in ssl_options: 

644 if ssl_options["cert_reqs"] == ssl.CERT_NONE: 

645 # This may have been set automatically by PROTOCOL_TLS_CLIENT but is 

646 # incompatible with CERT_NONE so we must manually clear it. 

647 context.check_hostname = False 

648 context.verify_mode = ssl_options["cert_reqs"] 

649 if "ca_certs" in ssl_options: 

650 context.load_verify_locations(ssl_options["ca_certs"]) 

651 if "ciphers" in ssl_options: 

652 context.set_ciphers(ssl_options["ciphers"]) 

653 if hasattr(ssl, "OP_NO_COMPRESSION"): 

654 # Disable TLS compression to avoid CRIME and related attacks. 

655 # This constant depends on openssl version 1.0. 

656 # TODO: Do we need to do this ourselves or can we trust 

657 # the defaults? 

658 context.options |= ssl.OP_NO_COMPRESSION 

659 return context 

660 

661 

662def ssl_wrap_socket( 

663 socket: socket.socket, 

664 ssl_options: dict[str, Any] | ssl.SSLContext, 

665 server_hostname: str | None = None, 

666 server_side: bool | None = None, 

667 **kwargs: Any, 

668) -> ssl.SSLSocket: 

669 """Returns an ``ssl.SSLSocket`` wrapping the given socket. 

670 

671 ``ssl_options`` may be either an `ssl.SSLContext` object or a 

672 dictionary (as accepted by `ssl_options_to_context`). Additional 

673 keyword arguments are passed to `ssl.SSLContext.wrap_socket`. 

674 

675 .. versionchanged:: 6.2 

676 

677 Added server_side argument. Omitting this argument will 

678 result in a DeprecationWarning on Python 3.10. 

679 """ 

680 context = ssl_options_to_context(ssl_options, server_side=server_side) 

681 if server_side is None: 

682 server_side = False 

683 assert ssl.HAS_SNI 

684 # TODO: add a unittest for hostname validation (python added server-side SNI support in 3.4) 

685 # In the meantime it can be manually tested with 

686 # python3 -m tornado.httpclient https://sni.velox.ch 

687 return context.wrap_socket( 

688 socket, server_hostname=server_hostname, server_side=server_side, **kwargs 

689 )