Skip to content

gh-47711: Use connected source address for SMTP address literals - #159166

Open
ishu7w wants to merge 1 commit into
python:mainfrom
ishu7w:fix-smtp-ipv6-ehlo
Open

ishu7w wants to merge 1 commit into
python:mainfrom
ishu7w:fix-smtp-ipv6-ehlo

Conversation

@ishu7w

@ishu7w ishu7w commented Oct 11, 2026 •

Copy link
Copy Markdown

@ishu7w
ishu7w requested a review from a team as a code owner October 11, 2026 06:35
@python-cla-bot

python-cla-bot Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@read-the-docs-community

Copy link
Copy Markdown

Documentation build overview

📚 cpython-previews | 🛠️ Build #35069472 | 📁 Comparing a58dc50 against main (c6c0608)

  🔍 Preview build  

2 files changed
± library/smtplib.html
± whatsnew/changelog.html

Comment thread Doc/library/smtplib.rst
:func:`socket.getfqdn`. If the :meth:`connect` call returns anything other
:func:`socket.getfqdn`. If it is not fully qualified, the connected socket's
local IP address is used as an address literal instead. IPv6 addresses use
the ``[IPv6:address]`` form. If the :meth:`connect` call returns anything other

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure it is useful to call out IPv6 separately here, since what we are doing is (now) following the standard, but I don't have a strong objection to including it.

Comment thread Lib/test/test_smtplib.py
)
for family, address, expected in cases:
for connect_in_init in (True, False):
with self.subTest(family=family, connect_in_init=connect_in_init):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to include address in the subtest description.

Comment thread Lib/smtplib.py
except socket.gaierror:
pass
self.local_hostname = '[%s]' % addr

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, the second part of this inner else is now code that doesn't have any effect, since local_hostname then gets reset unconditionally in connect. I suggest the following:

        self.local_hostname = local_hostname
        if local_hostname is None:
            # RFC 2821 says we should use the fqdn in the EHLO/HELO verb, and
            # if that can't be calculated, that we should use a domain literal
            # instead (essentially an encoded IP address like [A.B.C.D]).
            fqdn = socket.getfqdn()
            if '.' in fqdn:
                self.local_hostname = fqdn
            else:
                # We can't find an fqdn hostname, so we'll compute the
                # correct domain literal in connect.
                self._find_local_hostname = True

And then of course changing _use_socket_hostname to _find_local_hostname in connect. What do you think? The tests, including the new ones, still pass.

Comment thread Lib/smtplib.py
if code == 220 and self._use_socket_hostname:
addr = self.sock.getsockname()[0]
if self.sock.family == socket.AF_INET6:
# An SMTP address literal does not include a scope identifier.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For clarity, let's add: but does require an IPv6 "tag" per RFC 5321 Section 4.1.3

Comment thread Lib/smtplib.py
if self.sock.family == socket.AF_INET6:
# An SMTP address literal does not include a scope identifier.
addr = 'IPv6:' + addr.split('%', 1)[0]
self.local_hostname = '[%s]' % addr

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be clearer to use an f-string here.

@@ -0,0 +1,3 @@
When no fully qualified hostname is available, :mod:`smtplib` now uses the
connected socket's local address for the HELO/EHLO address literal, including
properly formatted IPv6 literals.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add: "Previously IPv6 was not handled correctly."

@bedevere-app

bedevere-app Bot commented Oct 15, 2026

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants