Repository: secdev/scapy

Issue:
conf.route.get_if_bcast(interface) uses mask from link-local route
#### Brief description

I'm trying to use conf.route.get_if_bcast(interface) to obtain the broadcast address of my local network. But the result seems to be using the subnet mask of the link-local subnet rather than the expected one.

#### Environment

-   Scapy version: 2.4.3
-   Python version: 3.7.7
-   Operating System: macOS 10.15.4

#### How to reproduce

```
(interface, localIP, defaultGateway) = conf.route.route("0.0.0.0")
# ('en0', '172.21.230.10', '172.21.230.1')
defaultGatewayIP = conf.route.get_if_bcast(interface) 
print(defaultGatewayIP)
```
#### Actual result

```172.21.255.255```

#### Expected result

```172.21.230.255```
I use a /24 subnet in my local network, but the broadcast address was calculated using a /16 subnet mask

#### Related resources

Looking at [the code](https://github.com/secdev/scapy/blob/cde3ae6b49adedfcbe01c1a8b4bb0b992d2022ee/scapy/route.py#L180) and my routing table, the default route mask seems to be coming from the link-local interface as it's the first entry with a matching interface where (net != 0)?
```
>>> for route in conf.route.routes:
...     (net, msk, gw, iface, addr, metric) = route
...     print(ltoa(net), ltoa(msk), gw, iface, addr, metric)
... 
0.0.0.0 0.0.0.0 172.21.230.1 en0 172.21.230.10 1
0.0.0.0 0.0.0.0 172.21.230.1 en1 172.21.230.11 1
127.0.0.0 255.0.0.0 0.0.0.0 lo0 127.0.0.1 1
127.0.0.1 255.255.255.255 0.0.0.0 lo0 127.0.0.1 1
169.254.0.0 255.255.0.0 0.0.0.0 en0 172.21.230.10 1 <== mask selected
169.254.0.0 255.255.0.0 0.0.0.0 en1 172.21.230.11 1
172.21.230.0 255.255.255.0 0.0.0.0 en0 172.21.230.10 1 <== mask expected
172.21.230.0 255.255.255.0 0.0.0.0 en1 172.21.230.11 1
172.21.230.1 255.255.255.255 0.0.0.0 en0 172.21.230.10 1
172.21.230.1 255.255.255.255 0.0.0.0 en0 172.21.230.10 1
172.21.230.1 255.255.255.255 0.0.0.0 en1 172.21.230.11 1
172.21.230.1 255.255.255.255 0.0.0.0 en1 172.21.230.11 1
```

Does this require another exception to skip the link-local network as well?

e.g. 

```
if (net == 0) or (net == 2851995648):
    # Skip default or link-local routes
    continue
```
conf.route.get_if_bcast(iface) should return all valid broadcast addresses
**Checklist:**

-   [X] If you are new to Scapy: I have checked [CONTRIBUTING.md](https://github.com/secdev/scapy/blob/master/CONTRIBUTING.md) (esp. section submitting-pull-requests)
-   [X] I squashed commits belonging together
-   [X] I added unit tests or explained why they are not relevant
-   [X] I executed the regression tests for Python2 and Python3 (using `tox` and `cd test && ./run_tests`)
-   [X] If the PR is still not finished, please create a [Draft Pull Request](https://github.blog/2019-02-14-introducing-draft-pull-requests/)

Skips past any route that is 0.0.0.0 or included in 127.0.0.0/8 (loopback), 224.0.0.0/4 (multicast) or 169.254.0.0/16 (link local).

I don't believe additional unit tests are appropriate but I'm happy to be corrected on that.

fixes #2615



Discussion:

Resolving patch:
diff --git a/scapy/layers/l2.py b/scapy/layers/l2.py
--- a/scapy/layers/l2.py
+++ b/scapy/layers/l2.py
@@ -79,7 +79,7 @@ def getmacbyip(ip, chainCC=0):
     if (tmp[0] & 0xf0) == 0xe0:  # mcast @
         return "01:00:5e:%.2x:%.2x:%.2x" % (tmp[1] & 0x7f, tmp[2], tmp[3])
     iff, _, gw = conf.route.route(ip)
-    if ((iff == conf.loopback_name) or (ip == conf.route.get_if_bcast(iff))):  # noqa: E501
+    if (iff == conf.loopback_name) or (ip in conf.route.get_if_bcast(iff)):
         return "ff:ff:ff:ff:ff:ff"
     if gw != "0.0.0.0":
         ip = gw
diff --git a/scapy/route.py b/scapy/route.py
--- a/scapy/route.py
+++ b/scapy/route.py
@@ -178,17 +178,22 @@ def route(self, dst=None, verbose=conf.verb):
         return ret
 
     def get_if_bcast(self, iff):
+        bcast_list = []
         for net, msk, gw, iface, addr, metric in self.routes:
             if net == 0:
-                continue
+                continue    # Ignore default route "0.0.0.0"
+            elif msk == 0xffffffff:
+                continue    # Ignore host-specific routes
             if scapy.consts.WINDOWS:
                 if iff.guid != iface.guid:
                     continue
             elif iff != iface:
                 continue
-            bcast = atol(addr) | (~msk & 0xffffffff)  # FIXME: check error in atol()  # noqa: E501
-            return ltoa(bcast)
-        warning("No broadcast address found for iface %s\n", iff)
+            bcast = net | (~msk & 0xffffffff)
+            bcast_list.append(ltoa(bcast))
+        if not bcast_list:
+            warning("No broadcast address found for iface %s\n", iff)
+        return bcast_list
 
 
 conf.route = Route()


Test patch:
diff --git a/test/regression.uts b/test/regression.uts
--- a/test/regression.uts
+++ b/test/regression.uts
@@ -11373,6 +11373,20 @@ r4.get_if_bcast(get_dummy_interface()) == "1.2.3.255"
 r4.ifdel(get_dummy_interface())
 len(r4.routes) == len_r4
 
+dummy_interface = get_dummy_interface()
+
+conf.route.routes = [
+    (0, 0, '172.21.230.1', dummy_interface, '172.21.230.10', 1),                # 0.0.0.0         / 0.0.0.0         == 255.255.255.255
+    (2851995648, 4294901760, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 169.254.0.0     / 255.255.0.0     == 169.254.255.255
+    (2887116288, 4294967040, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 172.21.230.0    / 255.255.255.0   == 172.21.230.255
+    (2887116289, 4294967295, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 172.21.230.1    / 255.255.255.255 == 172.21.230.1
+    (3758096384, 4026531840, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 224.0.0.0       / 240.0.0.0       == 239.255.255.255
+    (3758096635, 4294967295, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 224.0.0.251     / 255.255.255.255 == 224.0.0.251
+    (4294967295, 4294967295, '0.0.0.0', dummy_interface, '172.21.230.10', 1),   # 255.255.255.255 / 255.255.255.255 == 255.255.255.255
+    ]
+
+assert sorted(conf.route.get_if_bcast(dummy_interface)) == sorted(['169.254.255.255', '172.21.230.255', '239.255.255.255'])
+conf.route.resync()
 
 ############
 ############