Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-gcemetadata for openSUSE:Factory checked in at 2026-09-12 21:21:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-gcemetadata (Old) and /work/SRC/openSUSE:Factory/.python-gcemetadata.new.1265 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-gcemetadata" Sat Sep 12 21:21:23 2026 rev:6 rq:1377451 version:1.1.2 Changes: -------- --- /work/SRC/openSUSE:Factory/python-gcemetadata/python-gcemetadata.changes 2026-07-14 13:57:11.697937992 +0200 +++ /work/SRC/openSUSE:Factory/.python-gcemetadata.new.1265/python-gcemetadata.changes 2026-09-12 21:25:11.774129465 +0200 @@ -1,0 +2,7 @@ +Fri Sep 4 18:05:43 UTC 2026 - Robert Schweikert <[email protected]> + +- Update to version 1.1.2 (bsc#1261908) + + Avoid connection attempt to black hole. Handle IPv6 and IPv4 access + to the metadata server giving IPv6 preference + +------------------------------------------------------------------- Old: ---- gcemetadata-1.1.1.tar.bz2 New: ---- gcemetadata-1.1.2.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-gcemetadata.spec ++++++ --- /var/tmp/diff_new_pack.pNLKVm/_old 2026-09-12 21:25:12.738169927 +0200 +++ /var/tmp/diff_new_pack.pNLKVm/_new 2026-09-12 21:25:12.742170095 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-gcemetadata # -# Copyright (c) 2026 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -25,7 +25,7 @@ %define upstream_name gcemetadata Name: python-gcemetadata -Version: 1.1.1 +Version: 1.1.2 Release: 0 Summary: Python module for collecting instance metadata from GCE License: GPL-3.0-or-later ++++++ gcemetadata-1.1.1.tar.bz2 -> gcemetadata-1.1.2.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gcemetadata-1.1.1/lib/gcemetadata/VERSION new/gcemetadata-1.1.2/lib/gcemetadata/VERSION --- old/gcemetadata-1.1.1/lib/gcemetadata/VERSION 2026-07-06 15:10:47.338017245 +0200 +++ new/gcemetadata-1.1.2/lib/gcemetadata/VERSION 2026-09-04 20:05:12.929394460 +0200 @@ -1 +1 @@ -1.1.1 +1.1.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/gcemetadata-1.1.1/lib/gcemetadata/gcemetadata.py new/gcemetadata-1.1.2/lib/gcemetadata/gcemetadata.py --- old/gcemetadata-1.1.1/lib/gcemetadata/gcemetadata.py 2026-07-06 15:10:47.338017245 +0200 +++ new/gcemetadata-1.1.2/lib/gcemetadata/gcemetadata.py 2026-09-04 20:05:12.929394460 +0200 @@ -16,6 +16,7 @@ # along with gcemetadata. If not, see <http://www.gnu.org/licenses/>. import os +import selectors import socket import time import urllib.request, urllib.error, urllib.parse @@ -53,8 +54,8 @@ self.server = 'metadata.google.internal' self.subnet_id = -1 - if not self._test_connectivity(self.server, 80): - msg = 'Could not connect to: %s' % self.server + if not self._test_connectivity(80): + msg = 'Could not connect to: metadata.google.internal' raise GCEMetadataException(msg) if apiv not in self.get_available_api_versions(): @@ -63,18 +64,85 @@ self._create_options_map() - def _test_connectivity(self, addr, port): - for i in range(6): - s = socket.socket() - try: - s.connect((addr, port)) - s.close() + def _test_connectivity(self, port): + # In a dual stack setting a request to the DNS name takes forever + # measured >40 seconds. We do not want to wait this long. Use the + # IP addresses for the metadata server. + for i in range(4): + server = self._connect_first_responder(port) + if server: + if ':' in server: + self.server = '[%s]' % server + else: + self.server = server return True - except socket.error: - time.sleep(1) + time.sleep(1) return False + def _connect_first_responder(self, port): + """Race a connection attempt to the IPv6 address of the metadata + service against an attempt to the IPv4 address, "Happy Eyeballs" + per RFC 8305. The IPv6 attempt is started first and the IPv4 + attempt follows if IPv6 has not completed within the connection + attempt delay. Return the address of the connection that is + established first.""" + metadata_srv_info = [ + (socket.AF_INET6, 'fd20:ce::254'), + (socket.AF_INET, '169.254.169.254') + ] + selector = selectors.DefaultSelector() + deadline = time.monotonic() + 5 # arbitrary timeout value + try: + while metadata_srv_info or selector.get_map(): + timeout = None + if metadata_srv_info: + family, addr = metadata_srv_info.pop(0) + sock = self._start_connect(family, addr, port) + if not sock: + # The attempt could not be started, no reason to + # wait before trying the next address + continue + selector.register(sock, selectors.EVENT_WRITE, addr) + timeout = 0.25 # The delay per RFC 8305 + time_left = deadline - time.monotonic() + if time_left <= 0: + return None + if timeout is None or timeout > time_left: + timeout = time_left + for key, event in selector.select(timeout): + error = key.fileobj.getsockopt( + socket.SOL_SOCKET, socket.SO_ERROR + ) + selector.unregister(key.fileobj) + key.fileobj.close() + if not error: + return key.data + finally: + for key in list(selector.get_map().values()): + key.fileobj.close() + selector.close() + + return None + + def _start_connect(self, family, addr, port): + """Start a non blocking connection attempt, return the socket the + attempt is running on or None if it could not be started""" + try: + sock = socket.socket(family, socket.SOCK_STREAM) + except OSError: + return None + sock.setblocking(False) + try: + sock.connect((addr, port)) + except BlockingIOError: + pass + except OSError: + sock.close() + return None + + return sock + def _add_arguments(self, option): """Add an argument to the uri""" arg_map = { @@ -164,7 +232,7 @@ """Return the value for the requested uri""" req = urllib.request.Request(url, headers=self.header) try: - value = urllib.request.urlopen(req).read() + value = urllib.request.urlopen(req, timeout=20).read() except: return None
