Repository: secdev/scapy

Issue:
Mac OS Catalina
**From:** https://stackoverflow.com/q/56936549/5459467

> I'm trying to create a sniffer from the scapy module. I'm running Python 3.7 on macOS Catalina. Assuming that it's something with the macOS Catalina public beta version. I just wanna know what's causing the problem if so. I'm trying hard to understand why scapy throws an index out of bounds trace:
> 
> **main.py**
> 
> ```
> import scapy.all as scapy
> 
> p = scapy.sniff()
> 
> p.summary()
> Result:
> 
> Traceback (most recent call last):
>   File "/Users/tom/PycharmProjects/sniffer/main.py", line 2, in <module>
>     import scapy.all as scapy
>   File "/Users/tom/PycharmProjects/sniffer/venv/lib/python3.7/site-packages/scapy/sendrecv.py", line 33, in <module>
>     import scapy.route  # noqa: F401
>   File "/Users/tom/PycharmProjects/sniffer/venv/lib/python3.7/site-packages/scapy/route.py", line 194, in <module>
>     conf.route = Route()
>   File "/Users/tom/PycharmProjects/sniffer/venv/lib/python3.7/site-packages/scapy/route.py", line 27, in __init__
>     self.resync()
>   File "/Users/tom/PycharmProjects/sniffer/venv/lib/python3.7/site-packages/scapy/route.py", line 35, in resync
>     self.routes = read_routes()
>   File "/Users/tom/PycharmProjects/sniffer/venv/lib/python3.7/site-packages/scapy/arch/unix.py", line 82, in read_routes
>     netif = rt[4 + mtu_present + prio_present + refs_present + locked]
> IndexError: list index out of range
> ```

I'd guess it is indeed related to Catalina, but am no expert. @guedou Could you please have a look?


Discussion:
I cannot do much without the output of ‘netstat -rn’ =\

Catalina is currently in beta and will be released in September 2019.

I had a friend try it out, here's the output:
http://dpaste.com/3TE9W3T
It does replicate the issue.

It would be nice if 2.4.3 stayed stable after the Catalina release.

Thanks! I was able to reproduce the issue.


Resolving patch:
diff --git a/scapy/arch/unix.py b/scapy/arch/unix.py
--- a/scapy/arch/unix.py
+++ b/scapy/arch/unix.py
@@ -51,6 +51,8 @@ def read_routes():
     ok = 0
     mtu_present = False
     prio_present = False
+    refs_present = False
+    use_present = False
     routes = []
     pending_if = []
     for line in f.readlines():
@@ -65,6 +67,7 @@ def read_routes():
                 mtu_present = "Mtu" in line
                 prio_present = "Prio" in line
                 refs_present = "Refs" in line
+                use_present = "Use" in line
             continue
         if not line:
             break
@@ -79,7 +82,9 @@ def read_routes():
             rt = line.split()
             dest, gw, flg = rt[:3]
             locked = OPENBSD and rt[6] == "L"
-            netif = rt[4 + mtu_present + prio_present + refs_present + locked]
+            offset = mtu_present + prio_present + refs_present + locked
+            offset += use_present
+            netif = rt[3 + offset]
         if flg.find("Lc") >= 0:
             continue
         if dest == "default":


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -7881,6 +7881,120 @@ default            link#11            UCSI            1        0 bridge1
 test_osx_netstat_truncated()
 
 
+= macOS 10.13
+~ mock_read_routes_bsd
+
+import mock
+from io import StringIO
+
+@mock.patch("scapy.arch.unix.get_if_addr")
+@mock.patch("scapy.arch.unix.os")
+def test_osx_10_13_ipv4(mock_os, mock_get_if_addr):
+    """Test read_routes() on OS X 10.13"""
+    # 'netstat -rn -f inet' output
+    netstat_output = u"""
+Routing tables
+
+Internet:
+Destination        Gateway            Flags        Refs      Use   Netif Expire
+default            192.168.28.1       UGSc           82        0     en0
+127                127.0.0.1          UCS             0        0     lo0
+127.0.0.1          127.0.0.1          UH              1      878     lo0
+169.254            link#5             UCS             0        0     en0
+192.168.28         link#5             UCS             4        0     en0
+192.168.28.1/32    link#5             UCS             2        0     en0
+192.168.28.1       88:32:9c:f5:4e:ea  UHLWIir        40       37     en0   1177
+192.168.28.2       62:aa:56:4b:51:54  UHLWI           0        0     en0    619
+192.168.28.4       38:17:ed:9a:58:28  UHLWIi          1        6     en0    428
+192.168.28.18/32   link#5             UCS             1        0     en0
+192.168.28.18      88:32:9c:f5:4e:eb  UHLWI           0        1     lo0
+192.168.28.28      04:0e:eb:11:74:a7  UHLWI           0        0     en0    576
+224.0.0/4          link#5             UmCS            1        0     en0
+224.0.0.251        1:0:5e:0:0:fb      UHmLWI          0        0     en0
+255.255.255.255/32 link#5             UCS             0        0     en0
+"""
+    # Mocked file descriptor
+    strio = StringIO(netstat_output)
+    mock_os.popen = mock.MagicMock(return_value=strio)
+    # Mocked get_if_addr() output
+    def se_get_if_addr(iface):
+        """Perform specific side effects"""
+        import socket
+        if iface == "en0":
+            return "192.168.28.18"
+        return "127.0.0.1"
+    mock_get_if_addr.side_effect = se_get_if_addr
+    # Test the function
+    from scapy.arch.unix import read_routes
+    scapy.arch.unix.DARWIN = False
+    scapy.arch.unix.FREEBSD = True
+    scapy.arch.unix.NETBSD = False
+    scapy.arch.unix.OPENBSD = False
+    routes = read_routes()
+    for r in routes:
+        print(r)
+    assert(len(routes) == 15)
+    default_route = [r for r in routes if r[0] == 0][0]
+    assert default_route[3] == "en0" and default_route[4] == "192.168.28.18"
+
+test_osx_10_13_ipv4()
+
+
+= macOS 10.15
+~ mock_read_routes_bsd
+
+import mock
+from io import StringIO
+
+@mock.patch("scapy.arch.unix.get_if_addr")
+@mock.patch("scapy.arch.unix.os")
+def test_osx_10_15_ipv4(mock_os, mock_get_if_addr):
+    """Test read_routes() on OS X 10.15"""
+    # 'netstat -rn -f inet' output
+    netstat_output = u"""
+Routing tables
+
+Internet:
+Destination        Gateway            Flags        Netif Expire
+default            192.168.122.1      UGSc           en0       
+127                127.0.0.1          UCS            lo0       
+127.0.0.1          127.0.0.1          UH             lo0       
+169.254            link#8             UCS            en0      !
+192.168.122        link#8             UCS            en0      !
+192.168.122.1/32   link#8             UCS            en0      !
+192.168.122.1      52:54:0:c0:b7:af   UHLWIir        en0   1169
+192.168.122.63/32  link#8             UCS            en0      !
+224.0.0/4          link#8             UmCS           en0      !
+224.0.0.251        1:0:5e:0:0:fb      UHmLWI         en0       
+255.255.255.255/32 link#8             UCS            en0      !
+"""
+    # Mocked file descriptor
+    strio = StringIO(netstat_output)
+    mock_os.popen = mock.MagicMock(return_value=strio)
+    # Mocked get_if_addr() output
+    def se_get_if_addr(iface):
+        """Perform specific side effects"""
+        import socket
+        if iface == "en0":
+            return "192.168.122.42"
+        return "127.0.0.1"
+    mock_get_if_addr.side_effect = se_get_if_addr
+    # Test the function
+    from scapy.arch.unix import read_routes
+    scapy.arch.unix.DARWIN = False
+    scapy.arch.unix.FREEBSD = True
+    scapy.arch.unix.NETBSD = False
+    scapy.arch.unix.OPENBSD = False
+    routes = read_routes()
+    for r in routes:
+        print(r)
+    assert(len(routes) == 11)
+    default_route = [r for r in routes if r[0] == 0][0]
+    assert default_route[3] == "en0" and default_route[4] == "192.168.122.42"
+
+test_osx_10_15_ipv4()
+
+
 = OpenBSD 6.3
 ~ mock_read_routes_bsd