Skip to content

Commit 87d5cd1

Browse files
authored
Merge pull request #1632 from kaiyou/master
allow ipv6 :: notation in split_port (using re)
2 parents e50eacd + 0c12713 commit 87d5cd1

File tree

2 files changed

+46
-67
lines changed

2 files changed

+46
-67
lines changed

docker/utils/ports.py

Lines changed: 40 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
import re
2+
3+
PORT_SPEC = re.compile(
4+
"^" # Match full string
5+
"(" # External part
6+
"((?P<host>[a-fA-F\d.:]+):)?" # Address
7+
"(?P<ext>[\d]*)(-(?P<ext_end>[\d]+))?:" # External range
8+
")?"
9+
"(?P<int>[\d]+)(-(?P<int_end>[\d]+))?" # Internal range
10+
"(?P<proto>/(udp|tcp))?" # Protocol
11+
"$" # Match full string
12+
)
13+
114

215
def add_port_mapping(port_bindings, internal_port, external):
316
if internal_port in port_bindings:
@@ -24,81 +37,41 @@ def build_port_bindings(ports):
2437
return port_bindings
2538

2639

27-
def to_port_range(port, randomly_available_port=False):
28-
if not port:
29-
return None
30-
31-
protocol = ""
32-
if "/" in port:
33-
parts = port.split("/")
34-
if len(parts) != 2:
35-
_raise_invalid_port(port)
36-
37-
port, protocol = parts
38-
protocol = "/" + protocol
39-
40-
if randomly_available_port:
41-
return ["%s%s" % (port, protocol)]
42-
43-
parts = str(port).split('-')
44-
45-
if len(parts) == 1:
46-
return ["%s%s" % (port, protocol)]
47-
48-
if len(parts) == 2:
49-
full_port_range = range(int(parts[0]), int(parts[1]) + 1)
50-
return ["%s%s" % (p, protocol) for p in full_port_range]
51-
52-
raise ValueError('Invalid port range "%s", should be '
53-
'port or startport-endport' % port)
54-
55-
5640
def _raise_invalid_port(port):
5741
raise ValueError('Invalid port "%s", should be '
5842
'[[remote_ip:]remote_port[-remote_port]:]'
5943
'port[/protocol]' % port)
6044

6145

62-
def split_port(port):
63-
parts = str(port).split(':')
64-
65-
if not 1 <= len(parts) <= 3:
66-
_raise_invalid_port(port)
67-
68-
if len(parts) == 1:
69-
internal_port, = parts
70-
if not internal_port:
71-
_raise_invalid_port(port)
72-
return to_port_range(internal_port), None
73-
if len(parts) == 2:
74-
external_port, internal_port = parts
75-
76-
internal_range = to_port_range(internal_port)
77-
if internal_range is None:
78-
_raise_invalid_port(port)
79-
80-
external_range = to_port_range(external_port, len(internal_range) == 1)
81-
if external_range is None:
82-
_raise_invalid_port(port)
83-
84-
if len(internal_range) != len(external_range):
85-
raise ValueError('Port ranges don\'t match in length')
86-
87-
return internal_range, external_range
46+
def port_range(start, end, proto, randomly_available_port=False):
47+
if not start:
48+
return start
49+
if not end:
50+
return [start + proto]
51+
if randomly_available_port:
52+
return ['{}-{}'.format(start, end) + proto]
53+
return [str(port) + proto for port in range(int(start), int(end) + 1)]
8854

89-
external_ip, external_port, internal_port = parts
9055

91-
if not internal_port:
56+
def split_port(port):
57+
match = PORT_SPEC.match(port)
58+
if match is None:
9259
_raise_invalid_port(port)
60+
parts = match.groupdict()
9361

94-
internal_range = to_port_range(internal_port)
95-
external_range = to_port_range(external_port, len(internal_range) == 1)
96-
97-
if not external_range:
98-
external_range = [None] * len(internal_range)
99-
100-
if len(internal_range) != len(external_range):
101-
raise ValueError('Port ranges don\'t match in length')
62+
host = parts['host']
63+
proto = parts['proto'] or ''
64+
internal = port_range(parts['int'], parts['int_end'], proto)
65+
external = port_range(
66+
parts['ext'], parts['ext_end'], '', len(internal) == 1)
10267

103-
return internal_range, [(external_ip, ex_port or None)
104-
for ex_port in external_range]
68+
if host is None:
69+
if external is not None and len(internal) != len(external):
70+
raise ValueError('Port ranges don\'t match in length')
71+
return internal, external
72+
else:
73+
if not external:
74+
external = [None] * len(internal)
75+
elif len(internal) != len(external):
76+
raise ValueError('Port ranges don\'t match in length')
77+
return internal, [(host, ext_port) for ext_port in external]

tests/unit/utils_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,12 @@ def test_split_port_range_with_protocol(self):
552552
self.assertEqual(external_port,
553553
[("127.0.0.1", "1000"), ("127.0.0.1", "1001")])
554554

555+
def test_split_port_with_ipv6_address(self):
556+
internal_port, external_port = split_port(
557+
"2001:abcd:ef00::2:1000:2000")
558+
self.assertEqual(internal_port, ["2000"])
559+
self.assertEqual(external_port, [("2001:abcd:ef00::2", "1000")])
560+
555561
def test_split_port_invalid(self):
556562
self.assertRaises(ValueError,
557563
lambda: split_port("0.0.0.0:1000:2000:tcp"))

0 commit comments

Comments
 (0)