Merge lp:~cjwatson/txpkgupload/ipv4-mapped-pasv into lp:~lazr-developers/txpkgupload/trunk

Proposed by Colin Watson
Status: Merged
Merged at revision: 46
Proposed branch: lp:~cjwatson/txpkgupload/ipv4-mapped-pasv
Merge into: lp:~lazr-developers/txpkgupload/trunk
Diff against target: 95 lines (+47/-2)
3 files modified
requirements.txt (+1/-1)
setup.py (+1/-0)
src/txpkgupload/twistedftp.py (+45/-1)
To merge this branch: bzr merge lp:~cjwatson/txpkgupload/ipv4-mapped-pasv
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+369996@code.launchpad.net

Commit message

Fix behaviour of PASV with IPv4-mapped addresses.

Description of the change

When receiving a connection to an IPv6 endpoint using an IPv4-mapped address (possible on some IPv6 stacks), PASV can work but needs to return a properly-encoded IPv4 address. Including the leading "::ffff:" in the response appears to confuse connection tracking in some firewalls.

This is a backport from a commit in https://github.com/twisted/twisted/pull/1149, with the only changes being minor adjustments to symbol names and using six.ensure_text rather than the private twisted.python.compat._coercedUnicode. As before, getting the tests backported here was unreasonably difficult, but the Twisted PR has reasonable coverage.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'requirements.txt'
2--- requirements.txt 2018-07-04 10:40:11 +0000
3+++ requirements.txt 2019-07-11 10:59:40 +0000
4@@ -35,7 +35,7 @@
5 python-mimeparse==0.1.4
6 pytz==2017.2
7 PyYAML==3.10
8-six==1.9.0
9+six==1.12.0
10 testresources==0.2.7
11 testtools==2.3.0
12 traceback2==1.4.0
13
14=== modified file 'setup.py'
15--- setup.py 2018-07-04 10:04:49 +0000
16+++ setup.py 2019-07-11 10:59:40 +0000
17@@ -54,6 +54,7 @@
18 'oops-twisted>=0.0.7',
19 'PyYAML',
20 'setuptools',
21+ 'six>=1.12.0',
22 'Twisted[conch]',
23 'zope.component',
24 'zope.interface>=3.6.0',
25
26=== modified file 'src/txpkgupload/twistedftp.py'
27--- src/txpkgupload/twistedftp.py 2019-05-31 16:40:15 +0000
28+++ src/txpkgupload/twistedftp.py 2019-07-11 10:59:40 +0000
29@@ -9,10 +9,12 @@
30 'FTPRealm',
31 ]
32
33+import ipaddress
34 import os
35 import re
36 import tempfile
37
38+import six
39 from twisted.application import (
40 service,
41 strports,
42@@ -198,10 +200,52 @@
43 (self.passivePortRange,))
44
45 def ftp_PASV(self):
46+ """
47+ Request for a passive connection
48+
49+ from the rfc::
50+
51+ This command requests the server-DTP to \"listen\" on a data port
52+ (which is not its default data port) and to wait for a connection
53+ rather than initiate one upon receipt of a transfer command. The
54+ response to this command includes the host and port address this
55+ server is listening on.
56+ """
57 if self.epsvAll:
58 return defer.fail(ftp.BadCmdSequenceError(
59 'may not send PASV after EPSV ALL'))
60- return ftp.FTP.ftp_PASV(self)
61+
62+ host = self.transport.getHost().host
63+ try:
64+ address = ipaddress.IPv6Address(six.ensure_text(host))
65+ except ipaddress.AddressValueError:
66+ pass
67+ else:
68+ if address.ipv4_mapped is not None:
69+ # IPv4-mapped addresses are usable, but we need to make sure
70+ # they're encoded as IPv4 in the response.
71+ host = str(address.ipv4_mapped)
72+ else:
73+ # There's no standard defining the behaviour of PASV with
74+ # IPv6, so just claim it as unimplemented. (Some servers
75+ # return something like '0,0,0,0' in the host part of the
76+ # response in order that at least clients that ignore the
77+ # host part can work, and if it becomes necessary then we
78+ # could do that too.)
79+ return defer.fail(ftp.CmdNotImplementedError('PASV'))
80+
81+ # if we have a DTP port set up, lose it.
82+ if self.dtpFactory is not None:
83+ # cleanupDTP sets dtpFactory to none. Later we'll do
84+ # cleanup here or something.
85+ self.cleanupDTP()
86+ self.dtpFactory = ftp.DTPFactory(pi=self)
87+ self.dtpFactory.setTimeout(self.dtpTimeout)
88+ self.dtpPort = self.getDTPPort(self.dtpFactory)
89+
90+ port = self.dtpPort.getHost().port
91+ self.reply(ftp.ENTERING_PASV_MODE, ftp.encodeHostPort(host, port))
92+ return self.dtpFactory.deferred.addCallback(lambda ign: None)
93
94 def _validateNetworkProtocol(self, protocol):
95 """

Subscribers

People subscribed via source and target branches

to all changes: