summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/moznetwork
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mozbase/moznetwork')
-rw-r--r--testing/mozbase/moznetwork/moznetwork/__init__.py26
-rw-r--r--testing/mozbase/moznetwork/moznetwork/moznetwork.py212
-rw-r--r--testing/mozbase/moznetwork/setup.py36
-rw-r--r--testing/mozbase/moznetwork/tests/manifest.ini3
-rw-r--r--testing/mozbase/moznetwork/tests/test_moznetwork.py73
5 files changed, 350 insertions, 0 deletions
diff --git a/testing/mozbase/moznetwork/moznetwork/__init__.py b/testing/mozbase/moznetwork/moznetwork/__init__.py
new file mode 100644
index 0000000000..f4e1468ccf
--- /dev/null
+++ b/testing/mozbase/moznetwork/moznetwork/__init__.py
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+moznetwork is a very simple module designed for one task: getting the
+network address of the current machine.
+
+Example usage:
+
+::
+
+ import moznetwork
+
+ try:
+ ip = moznetwork.get_ip()
+ print "The external IP of your machine is '%s'" % ip
+ except moznetwork.NetworkError:
+ print "Unable to determine IP address of machine"
+ raise
+
+"""
+
+from .moznetwork import get_ip
+
+__all__ = ["get_ip"]
diff --git a/testing/mozbase/moznetwork/moznetwork/moznetwork.py b/testing/mozbase/moznetwork/moznetwork/moznetwork.py
new file mode 100644
index 0000000000..74c79b1d5d
--- /dev/null
+++ b/testing/mozbase/moznetwork/moznetwork/moznetwork.py
@@ -0,0 +1,212 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import argparse
+import array
+import re
+import socket
+import struct
+import subprocess
+import sys
+
+import mozinfo
+import mozlog
+import six
+
+if mozinfo.isLinux:
+ import fcntl
+if mozinfo.isWin:
+ import os
+
+
+class NetworkError(Exception):
+ """Exception thrown when unable to obtain interface or IP."""
+
+
+def _get_logger():
+ logger = mozlog.get_default_logger(component="moznetwork")
+ if not logger:
+ logger = mozlog.unstructured.getLogger("moznetwork")
+ return logger
+
+
+def _get_interface_list():
+ """Provides a list of available network interfaces
+ as a list of tuples (name, ip)"""
+ logger = _get_logger()
+ logger.debug("Gathering interface list")
+ max_iface = 32 # Maximum number of interfaces(arbitrary)
+ bytes = max_iface * 32
+ is_32bit = (8 * struct.calcsize("P")) == 32 # Set Architecture
+ struct_size = 32 if is_32bit else 40
+
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ names = array.array("B", b"\0" * bytes)
+ outbytes = struct.unpack(
+ "iL",
+ fcntl.ioctl(
+ s.fileno(),
+ 0x8912, # SIOCGIFCONF
+ struct.pack("iL", bytes, names.buffer_info()[0]),
+ ),
+ )[0]
+ if six.PY3:
+ namestr = names.tobytes()
+ else:
+ namestr = names.tostring()
+ return [
+ (
+ six.ensure_str(namestr[i : i + 32].split(b"\0", 1)[0]),
+ socket.inet_ntoa(namestr[i + 20 : i + 24]),
+ )
+ for i in range(0, outbytes, struct_size)
+ ]
+
+ except IOError:
+ raise NetworkError("Unable to call ioctl with SIOCGIFCONF")
+
+
+def _proc_matches(args, regex):
+ """Helper returns the matches of regex in the output of a process created with
+ the given arguments"""
+ output = subprocess.Popen(
+ args=args,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ ).stdout.read()
+ return re.findall(regex, output)
+
+
+def _parse_ifconfig():
+ """Parse the output of running ifconfig on mac in cases other methods
+ have failed"""
+ logger = _get_logger()
+ logger.debug("Parsing ifconfig")
+
+ # Attempt to determine the default interface in use.
+ default_iface = _proc_matches(
+ ["route", "-n", "get", "default"], r"interface: (\w+)"
+ )
+
+ if default_iface:
+ addr_list = _proc_matches(
+ ["ifconfig", default_iface[0]], r"inet (\d+.\d+.\d+.\d+)"
+ )
+ if addr_list:
+ logger.debug(
+ "Default interface: [%s] %s" % (default_iface[0], addr_list[0])
+ )
+ if not addr_list[0].startswith("127."):
+ return addr_list[0]
+
+ # Iterate over plausible interfaces if we didn't find a suitable default.
+ for iface in ["en%s" % i for i in range(10)]:
+ addr_list = _proc_matches(["ifconfig", iface], r"inet (\d+.\d+.\d+.\d+)")
+ if addr_list:
+ logger.debug("Interface: [%s] %s" % (iface, addr_list[0]))
+ if not addr_list[0].startswith("127."):
+ return addr_list[0]
+
+ # Just return any that isn't localhost. If we can't find one, we have
+ # failed.
+ addrs = _proc_matches(["ifconfig"], r"inet (\d+.\d+.\d+.\d+)")
+ try:
+ return [addr for addr in addrs if not addr.startswith("127.")][0]
+ except IndexError:
+ return None
+
+
+def _parse_powershell():
+ logger = _get_logger()
+ logger.debug("Parsing Get-NetIPAdress output via PowerShell")
+
+ try:
+ cmd = os.path.join(
+ os.environ.get("SystemRoot", "C:\\WINDOWS"),
+ "system32",
+ "windowspowershell",
+ "v1.0",
+ "powershell.exe",
+ )
+ output = subprocess.check_output(
+ [cmd, "(Get-NetIPAddress | Format-List -Property IPAddress)"]
+ ).decode("ascii")
+ ips = re.findall(r"IPAddress : (\d+.\d+.\d+.\d+)", output)
+ for ip in ips:
+ logger.debug("IPAddress: %s" % ip)
+ if not ip.startswith("127."):
+ return ip
+ return None
+ except FileNotFoundError:
+ return None
+
+
+def get_ip():
+ """Provides an available network interface address, for example
+ "192.168.1.3".
+
+ A `NetworkError` exception is raised in case of failure."""
+ logger = _get_logger()
+ try:
+ hostname = socket.gethostname()
+ try:
+ logger.debug("Retrieving IP for %s" % hostname)
+ ips = socket.gethostbyname_ex(hostname)[2]
+ except socket.gaierror: # for Mac OS X
+ hostname += ".local"
+ logger.debug("Retrieving IP for %s" % hostname)
+ ips = socket.gethostbyname_ex(hostname)[2]
+ if len(ips) == 1:
+ ip = ips[0]
+ elif len(ips) > 1:
+ logger.debug("Multiple addresses found: %s" % ips)
+ ip = None
+ else:
+ ip = None
+ except socket.gaierror:
+ # sometimes the hostname doesn't resolve to an ip address, in which
+ # case this will always fail
+ ip = None
+
+ if ip is None or ip.startswith("127."):
+ if mozinfo.isLinux:
+ interfaces = _get_interface_list()
+ for ifconfig in interfaces:
+ logger.debug("Interface: [%s] %s" % (ifconfig[0], ifconfig[1]))
+ if ifconfig[0] == "lo":
+ continue
+ else:
+ return ifconfig[1]
+ elif mozinfo.isMac:
+ ip = _parse_ifconfig()
+ elif mozinfo.isWin:
+ ip = _parse_powershell()
+
+ if ip is None:
+ raise NetworkError("Unable to obtain network address")
+
+ return ip
+
+
+def get_lan_ip():
+ """Deprecated. Please use get_ip() instead."""
+ return get_ip()
+
+
+def cli(args=sys.argv[1:]):
+ parser = argparse.ArgumentParser(description="Retrieve IP address")
+ mozlog.commandline.add_logging_group(
+ parser, include_formatters=mozlog.commandline.TEXT_FORMATTERS
+ )
+
+ args = parser.parse_args()
+ mozlog.commandline.setup_logging("mozversion", args, {"mach": sys.stdout})
+
+ _get_logger().info("IP address: %s" % get_ip())
+
+
+if __name__ == "__main__":
+ cli()
diff --git a/testing/mozbase/moznetwork/setup.py b/testing/mozbase/moznetwork/setup.py
new file mode 100644
index 0000000000..84940e3de0
--- /dev/null
+++ b/testing/mozbase/moznetwork/setup.py
@@ -0,0 +1,36 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from setuptools import setup
+
+PACKAGE_VERSION = "1.1.0"
+
+deps = [
+ "mozinfo",
+ "mozlog >= 6.0",
+]
+
+setup(
+ name="moznetwork",
+ version=PACKAGE_VERSION,
+ description="Library of network utilities for use in Mozilla testing",
+ long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
+ classifiers=[
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.5",
+ "Development Status :: 5 - Production/Stable",
+ ],
+ # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords="mozilla",
+ author="Mozilla Automation and Tools team",
+ author_email="tools@lists.mozilla.org",
+ url="https://wiki.mozilla.org/Auto-tools/Projects/Mozbase",
+ license="MPL",
+ packages=["moznetwork"],
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=deps,
+ entry_points={"console_scripts": ["moznetwork = moznetwork:cli"]},
+)
diff --git a/testing/mozbase/moznetwork/tests/manifest.ini b/testing/mozbase/moznetwork/tests/manifest.ini
new file mode 100644
index 0000000000..8057892c67
--- /dev/null
+++ b/testing/mozbase/moznetwork/tests/manifest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+subsuite = mozbase
+[test_moznetwork.py]
diff --git a/testing/mozbase/moznetwork/tests/test_moznetwork.py b/testing/mozbase/moznetwork/tests/test_moznetwork.py
new file mode 100644
index 0000000000..8a1ee31a2e
--- /dev/null
+++ b/testing/mozbase/moznetwork/tests/test_moznetwork.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+"""
+Unit-Tests for moznetwork
+"""
+
+import re
+import subprocess
+from distutils.spawn import find_executable
+from unittest import mock
+
+import mozinfo
+import moznetwork
+import mozunit
+import pytest
+
+
+@pytest.fixture(scope="session")
+def ip_addresses():
+ """List of IP addresses associated with the host."""
+
+ # Regex to match IPv4 addresses.
+ # 0-255.0-255.0-255.0-255, note order is important here.
+ regexip = re.compile(
+ "((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}"
+ "(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)"
+ )
+
+ commands = (
+ ["ip", "addr", "show"],
+ ["ifconfig"],
+ ["ipconfig"],
+ # Explicitly search '/sbin' because it doesn't always appear
+ # to be on the $PATH of all systems
+ ["/sbin/ip", "addr", "show"],
+ ["/sbin/ifconfig"],
+ )
+
+ cmd = None
+ for command in commands:
+ if find_executable(command[0]):
+ cmd = command
+ break
+ else:
+ raise OSError(
+ "No program for detecting ip address found! Ensure one of 'ip', "
+ "'ifconfig' or 'ipconfig' exists on your $PATH."
+ )
+
+ ps = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ standardoutput, _ = ps.communicate()
+
+ # Generate a list of IPs by parsing the output of ip/ifconfig
+ return [x.group() for x in re.finditer(regexip, standardoutput.decode("UTF-8"))]
+
+
+def test_get_ip(ip_addresses):
+ """Attempt to test the IP address returned by
+ moznetwork.get_ip() is valid"""
+ assert moznetwork.get_ip() in ip_addresses
+
+
+@pytest.mark.skipif(mozinfo.isWin, reason="Test is not supported in Windows")
+def test_get_ip_using_get_interface(ip_addresses):
+ """Test that the control flow path for get_ip() using
+ _get_interface_list() is works"""
+ with mock.patch("socket.gethostbyname") as byname:
+ # Force socket.gethostbyname to return None
+ byname.return_value = None
+ assert moznetwork.get_ip() in ip_addresses
+
+
+if __name__ == "__main__":
+ mozunit.main()