Repository: buildbot/buildbot

Issue:
buildbot-worker doesn't support ipv6 address for master connection
I'm trying the following command line:
```shell
/usr/bin/buildbot-worker create-worker . "[2001\:\:324]:9989" test toto
```
But it gives the following error:
```
Traceback (most recent call last):
  File "/usr/bin/buildbot-worker", line 11, in <module>
    load_entry_point('buildbot-worker==2.0.1.post2', 'console_scripts', 'buildbot-worker')()
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 238, in run
    config.parseOptions()
  File "/usr/lib/python3/dist-packages/twisted/python/usage.py", line 267, in parseOptions
    self.subOptions.parseOptions(rest)
  File "/usr/lib/python3/dist-packages/twisted/python/usage.py", line 273, in parseOptions
    self.parseArgs(*args)
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 183, in parseArgs
    self['host'], self['port'] = self.validateMasterArgument(master)
  File "/usr/lib/python3/dist-packages/buildbot_worker/scripts/runner.py", line 161, in validateMasterArgument
    master, port = master_arg.split(":")
ValueError: too many values to unpack (expected 2)
```

Is Ipv6 address supposed to be supported?



Discussion:
I think the fact that this does not work is a bug either way :) Thanks for the issue.

In addition, I've found that using a DNS entry with only an ipv6 address prevent buildbot to connect to master. twist.log contains the following message:
```
2020-01-13 21:03:47+0000 [-] Connecting example.org:9989
2020-01-13 21:03:47+0000 [-] Connection example.org:9989 failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.DNSLookupError'>: DNS lookup failed: example.org.
```
It worked after modifying buildbot.tac to use a ipv6 address.

Resolving patch:
diff --git a/worker/buildbot_worker/scripts/runner.py b/worker/buildbot_worker/scripts/runner.py
--- a/worker/buildbot_worker/scripts/runner.py
+++ b/worker/buildbot_worker/scripts/runner.py
@@ -158,11 +158,24 @@ def validateMasterArgument(self, master_arg):
         if master_arg[:5] == "http:":
             raise usage.UsageError("<master> is not a URL - do not use URL")
 
-        if ":" not in master_arg:
+        if master_arg.startswith("[") and "]" in master_arg:
+            # detect ipv6 address with format [2001:1:2:3:4::1]:4321
+            master, port_tmp = master_arg.split("]")
+            master = master[1:]
+            if ":" not in port_tmp:
+                port = 9989
+            else:
+                port = port_tmp.split(":")[1]
+
+        elif ":" not in master_arg:
             master = master_arg
             port = 9989
         else:
-            master, port = master_arg.split(":")
+            try:
+                master, port = master_arg.split(":")
+            except ValueError:
+                raise usage.UsageError("invalid <master> argument '{}', "
+                                       "if it is an ipv6 address, it must be enclosed by []".format(master_arg))
 
         if not master:
             raise usage.UsageError("invalid <master> argument '{}'".format(


Test patch:
diff --git a/worker/buildbot_worker/test/unit/test_scripts_runner.py b/worker/buildbot_worker/test/unit/test_scripts_runner.py
--- a/worker/buildbot_worker/test/unit/test_scripts_runner.py
+++ b/worker/buildbot_worker/test/unit/test_scripts_runner.py
@@ -282,13 +282,64 @@ def test_validateMasterArgument_inv_port(self):
     def test_validateMasterArgument_ok(self):
         """
         test calling CreateWorkerOptions.validateMasterArgument()
-        on <master> without host and port parts specified.
+        on <master> with host and port parts specified.
         """
         opts = runner.CreateWorkerOptions()
         self.assertEqual(opts.validateMasterArgument("mstrhost:4321"),
                          ("mstrhost", 4321),
                          "incorrect master host and/or port")
 
+    def test_validateMasterArgument_ipv4(self):
+        """
+        test calling CreateWorkerOptions.validateMasterArgument()
+        on <master> with ipv4 host specified.
+        """
+        opts = runner.CreateWorkerOptions()
+        self.assertEqual(opts.validateMasterArgument("192.168.0.0"),
+                         ("192.168.0.0", 9989),
+                         "incorrect master host and/or port")
+
+    def test_validateMasterArgument_ipv4_port(self):
+        """
+        test calling CreateWorkerOptions.validateMasterArgument()
+        on <master> with ipv4 host and port parts specified.
+        """
+        opts = runner.CreateWorkerOptions()
+        self.assertEqual(opts.validateMasterArgument("192.168.0.0:4321"),
+                         ("192.168.0.0", 4321),
+                         "incorrect master host and/or port")
+
+    def test_validateMasterArgument_ipv6(self):
+        """
+        test calling CreateWorkerOptions.validateMasterArgument()
+        on <master> with ipv6 host specified.
+        """
+        opts = runner.CreateWorkerOptions()
+        self.assertEqual(opts.validateMasterArgument("[2001:1:2:3:4::1]"),
+                         ("2001:1:2:3:4::1", 9989),
+                         "incorrect master host and/or port")
+
+    def test_validateMasterArgument_ipv6_port(self):
+        """
+        test calling CreateWorkerOptions.validateMasterArgument()
+        on <master> with ipv6 host and port parts specified.
+        """
+        opts = runner.CreateWorkerOptions()
+        self.assertEqual(opts.validateMasterArgument("[2001:1:2:3:4::1]:4321"),
+                         ("2001:1:2:3:4::1", 4321),
+                         "incorrect master host and/or port")
+
+    def test_validateMasterArgument_ipv6_no_bracket(self):
+        """
+        test calling CreateWorkerOptions.validateMasterArgument()
+        on <master> with ipv6 without [] specified.
+        """
+        opts = runner.CreateWorkerOptions()
+        with self.assertRaisesRegex(usage.UsageError,
+                              r"invalid <master> argument '2001:1:2:3:4::1:4321', "
+                              r"if it is an ipv6 address, it must be enclosed by \[\]"):
+            opts.validateMasterArgument("2001:1:2:3:4::1:4321")
+
 
 class TestOptions(misc.StdoutAssertionsMixin, unittest.TestCase):