├── .github └── workflows │ └── pythonpublish.yml ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── Run.py ├── Test.py ├── config └── config_example.yml ├── filter.h ├── pcap.c ├── pcap.pyx ├── pimdm ├── Config.py ├── Interface.py ├── InterfacePIM.py ├── InterfacePIM6.py ├── Kernel.py ├── Main.py ├── Neighbor.py ├── Run.py ├── TestLogger.py ├── UnicastRouting.py ├── __init__.py ├── custom_timer │ ├── RemainingTimer.py │ └── __init__.py ├── daemon │ ├── Daemon.py │ └── __init__.py ├── packet │ ├── Packet.py │ ├── PacketIpHeader.py │ ├── PacketPayload.py │ ├── PacketPimAssert.py │ ├── PacketPimEncodedGroupAddress.py │ ├── PacketPimEncodedSourceAddress.py │ ├── PacketPimEncodedUnicastAddress.py │ ├── PacketPimGraft.py │ ├── PacketPimGraftAck.py │ ├── PacketPimHeader.py │ ├── PacketPimHello.py │ ├── PacketPimHelloOptions.py │ ├── PacketPimJoinPrune.py │ ├── PacketPimJoinPruneMulticastGroup.py │ ├── PacketPimStateRefresh.py │ ├── ReceivedPacket.py │ └── __init__.py ├── rwlock │ ├── RWLock.py │ └── __init__.py ├── tree │ ├── KernelEntry.py │ ├── KernelEntryInterface.py │ ├── __init__.py │ ├── assert_state.py │ ├── data_packets_socket.py │ ├── downstream_prune.py │ ├── local_membership.py │ ├── metric.py │ ├── originator.py │ ├── pim_globals.py │ ├── tree_if_downstream.py │ ├── tree_if_upstream.py │ ├── tree_interface.py │ └── upstream_prune.py └── utils.py ├── requirements.txt └── setup.py /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-18.04 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine auditwheel 20 | sudo apt install libpcap-dev 21 | - name: Build and publish 22 | env: 23 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 24 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 25 | run: | 26 | python setup.py sdist bdist_wheel 27 | auditwheel repair dist/*.whl 28 | mv wheelhouse/* dist 29 | rm dist/*.whl 30 | twine upload dist/* 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pedro Oliveira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include filter.h 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PIM-DM 2 | 3 | [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pim-dm)](https://pypi.org/project/pim-dm/) 4 | [![PyPI](https://img.shields.io/pypi/v/pim-dm)](https://pypi.org/project/pim-dm/) 5 | [![PyPI - License](https://img.shields.io/pypi/l/pim-dm)](https://github.com/pedrofran12/pim_dm/blob/master/LICENSE) 6 | 7 | We have implemented PIM-DM specification ([RFC3973](https://tools.ietf.org/html/rfc3973)). 8 | 9 | This repository stores the implementation of this protocol. The implementation is written in Python language and is destined to Linux systems. 10 | 11 | Additionally, IGMPv2 and MLDv1 are implemented alongside with PIM-DM to detect interest of hosts. 12 | 13 | 14 | # Requirements 15 | 16 | - Linux machine 17 | - Unicast routing protocol 18 | - Python3 (we have written all code to be compatible with at least Python v3.3) 19 | - pip (to install all dependencies) 20 | - libpcap-dev 21 | - python3-dev 22 | - Cython (it can be installed with pip) 23 | 24 | 25 | # Installation 26 | 27 | ``` 28 | pip3 install pim-dm 29 | ``` 30 | 31 | 32 | 33 | # Run PIM-DM protocol 34 | 35 | You may need sudo permissions, in order to run this protocol. This is required because we use raw sockets to exchange control messages. For this reason, some sockets to work properly need to have super user permissions. 36 | 37 | To interact with the protocol you need to execute the `pim-dm` command. You may need to specify a command and corresponding arguments: 38 | 39 | `pim-dm -COMMAND ARGUMENTS` 40 | 41 | 42 | #### Start protocol process 43 | 44 | In order to start the protocol you first need to explicitly start it. This will start a daemon process, which will be running in the background. The command is the following: 45 | ``` 46 | sudo pim-dm -start [-mvrf MULTICAST_TABLE_ID] [-uvrf UNICAST_TABLE_ID] 47 | ``` 48 | 49 | IPv4 and IPv6 multicast is supported. By default all commands will be executed on IPv4 daemon. To execute a command on the IPv6 daemon use `-6`. 50 | 51 | We support multiple tables. Each daemon process will be bind to a given multicast and unicast table id, which can be defined at startup with `-mvrf` and `-uvrf`. 52 | 53 | If `-mvrf` is not defined, the default multicast table id will be used (table id 0). 54 | 55 | If `-uvrf` is not defined, the default unicast table id will be used (table id 254). 56 | 57 | After starting the protocol process, if the default multicast table is not used, the following commands (for adding interfaces and listing state) need to have the argument `-mvrf` defined to specify the corresponding daemon process. 58 | 59 | 60 | 61 | #### Multi daemon support 62 | 63 | Multiple daemons are supported, each bind to a given multicast routing table id. 64 | 65 | To perform configurations on one of these daemons use `-mvrf` command and define the daemon by its multicast table id. 66 | 67 | 68 | To see all daemons that are currently running: 69 | 70 | ``` 71 | sudo pim-dm -instances 72 | ``` 73 | 74 | #### Add interface 75 | 76 | After starting the protocol process you can enable the protocol in specific interfaces. You need to specify which interfaces will have IGMP enabled and which interfaces will have PIM-DM enabled. 77 | 78 | - To enable PIM-DM without State-Refresh, in a given interface, you need to run the following command: 79 | 80 | ``` 81 | sudo pim-dm -ai INTERFACE_NAME [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 82 | ``` 83 | 84 | - To enable PIM-DM with State-Refresh, in a given interface, you need to run the following command: 85 | 86 | ``` 87 | sudo pim-dm -aisr INTERFACE_NAME [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 88 | ``` 89 | 90 | - To enable IGMP/MLD in a given interface, you need to run the following command: 91 | 92 | - IGMP: 93 | ``` 94 | sudo pim-dm -aiigmp INTERFACE_NAME [-mvrf MULTICAST_TABLE_ID] 95 | ``` 96 | 97 | - MLD: 98 | ``` 99 | sudo pim-dm -aimld INTERFACE_NAME [-mvrf MULTICAST_TABLE_ID] 100 | ``` 101 | 102 | #### Remove interface 103 | 104 | To remove a previously added interface, you need run the following commands: 105 | 106 | - To remove a previously added PIM-DM interface: 107 | 108 | ``` 109 | sudo pim-dm -ri INTERFACE_NAME [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 110 | ``` 111 | 112 | - To remove a previously added IGMP/MLD interface: 113 | - IGMP: 114 | ``` 115 | sudo pim-dm -riigmp INTERFACE_NAME [-mvrf MULTICAST_TABLE_ID] 116 | ``` 117 | 118 | - MLD: 119 | ``` 120 | sudo pim-dm -rimld INTERFACE_NAME [-mvrf MULTICAST_TABLE_ID] 121 | ``` 122 | 123 | 124 | #### Stop protocol process 125 | 126 | If you want to stop the protocol process, and stop the daemon process, you need to explicitly run this command: 127 | 128 | If a specific multicast table id was defined on startup, you need to define the daemon by its multicast table id. 129 | 130 | ``` 131 | sudo pim-dm -stop [-mvrf MULTICAST_TABLE_ID] 132 | ``` 133 | 134 | 135 | 136 | ## Commands for monitoring the protocol process 137 | We have built some list commands that can be used to check the "internals" of the implementation. 138 | 139 | - #### List interfaces: 140 | 141 | Show all router interfaces and which ones have PIM-DM and IGMP/MLD enabled. For IGMP/MLD enabled interfaces you can check the Querier state. 142 | 143 | ``` 144 | sudo pim-dm -li [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 145 | ``` 146 | 147 | - #### List neighbors 148 | Verify neighbors that have established a neighborhood relationship. 149 | 150 | ``` 151 | sudo pim-dm -ln [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 152 | ``` 153 | 154 | - #### List state 155 | List all state machines and corresponding state of all trees that are being monitored. Also list IGMP state for each group being monitored. 156 | 157 | ``` 158 | sudo pim-dm -ls [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 159 | ``` 160 | 161 | - #### Multicast Routing Table 162 | List Linux Multicast Routing Table (equivalent to `ip mroute show`) 163 | 164 | ``` 165 | sudo pim-dm -mr [-4 | -6] [-mvrf MULTICAST_TABLE_ID] 166 | ``` 167 | 168 | ## Config File 169 | 170 | It is possible to configure the protocol using a YAML file. This configuration file can be used to set all interfaces that will have PIM-DM/IGMP/MLD enabled, as well to fine tune these protocols by setting their timers. Currently the settings are shared by all interfaces. In a future release it will be possible to set timers per interface. 171 | 172 | To use this feature you need to manually install PyYaml. PyYaml is not automatically installed with `pim-dm` to support older Python versions (as of now PyYaml requires at least Python v3.5). 173 | 174 | [This YAML file](https://github.com/pedrofran12/pim_dm/tree/master/config/config_example.yml) is a configuration file example. 175 | 176 | It it also possible to get an YAML configuration file from the current settings of the daemon. This will output an YAML template that can be used later for enabling the daemon with the same settings (enabled interfaces and timers). The command for this matter is the following: 177 | 178 | ``` 179 | sudo pim-dm -get_config [-mvrf MULTICAST_TABLE_ID] 180 | ``` 181 | 182 | To input an YAML configuration file to the daemon: 183 | 184 | ``` 185 | sudo pim-dm -config CONFIGURATION_FILE_PATH 186 | ``` 187 | 188 | 189 | ## Help command 190 | In order to determine which commands and corresponding arguments are available you can call the help command: 191 | 192 | ``` 193 | pim-dm -h 194 | ``` 195 | 196 | 197 | ## Tests 198 | 199 | We have performed tests to our PIM-DM implementation. You can check on the corresponding branches: 200 | 201 | - [Test_PIM_Hello](https://github.com/pedrofran12/pim_dm/tree/Test_PIM_Hello) - Topology used to test the establishment of adjacency between routers. 202 | - [Test_PIM_BroadcastTree](https://github.com/pedrofran12/pim_dm/tree/Test_PIM_BroadcastTree) - Topology used to test our implementation regarding the creation and maintenance of the broadcast tree. 203 | - [Test_PIM_Assert](https://github.com/pedrofran12/pim_dm/tree/Test_PIM_Assert) - Topology used to test the election of the AssertWinner. 204 | - [Test_PIM_Join_Prune_Graft](https://github.com/pedrofran12/pim_dm/tree/Test_PIM_Join_Prune_Graft) - Topology used to test the Pruning and Grafting of the multicast distribution tree. 205 | - [Test_PIM_StateRefresh](https://github.com/pedrofran12/pim_dm/tree/Test_PIM_StateRefresh) - Topology used to test PIM-DM State Refresh. 206 | - [Test_IGMP](https://github.com/pedrofran12/pim_dm/tree/Test_IGMP) - Topology used to test our IGMPv2 implementation. 207 | -------------------------------------------------------------------------------- /Run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from pimdm import Run 4 | 5 | if __name__ == '__main__': 6 | Run.main() -------------------------------------------------------------------------------- /Test.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | import struct 4 | # ficheiros importantes: /usr/include/linux/mroute.h 5 | 6 | MRT_BASE = 200 7 | MRT_INIT = (MRT_BASE) # Activate the kernel mroute code */ 8 | MRT_DONE = (MRT_BASE+1) #/* Shutdown the kernel mroute */ 9 | MRT_ADD_VIF = (MRT_BASE+2) #/* Add a virtual interface */ 10 | MRT_DEL_VIF = (MRT_BASE+3) #/* Delete a virtual interface */ 11 | MRT_ADD_MFC = (MRT_BASE+4) #/* Add a multicast forwarding entry */ 12 | MRT_DEL_MFC = (MRT_BASE+5) #/* Delete a multicast forwarding entry */ 13 | MRT_VERSION = (MRT_BASE+6) #/* Get the kernel multicast version */ 14 | MRT_ASSERT = (MRT_BASE+7) #/* Activate PIM assert mode */ 15 | MRT_PIM = (MRT_BASE+8) #/* enable PIM code */ 16 | MRT_TABLE = (MRT_BASE+9) #/* Specify mroute table ID */ 17 | MRT_ADD_MFC_PROXY = (MRT_BASE+10) #/* Add a (*,*|G) mfc entry */ 18 | MRT_DEL_MFC_PROXY = (MRT_BASE+11) #/* Del a (*,*|G) mfc entry */ 19 | MRT_MAX = (MRT_BASE+11) 20 | 21 | IGMPMSG_NOCACHE = 1 22 | IGMPMSG_WRONGVIF = 2 23 | IGMPMSG_WHOLEPKT = 3 24 | 25 | 26 | s2 = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IGMP) 27 | 28 | #MRT INIT 29 | s2.setsockopt(socket.IPPROTO_IP, MRT_INIT, 1) 30 | 31 | #MRT PIM 32 | s2.setsockopt(socket.IPPROTO_IP, MRT_PIM, 1) 33 | 34 | #ADD VIRTUAL INTERFACE 35 | #estrutura = struct.pack("HBBI 4s 4s", 1, 0x4, 0, 0, socket.inet_aton("192.168.1.112"), socket.inet_aton("224.1.1.112")) 36 | estrutura = struct.pack("HBBI 4s 4s", 0, 0x0, 1, 0, socket.inet_aton("10.0.0.1"), socket.inet_aton("0.0.0.0")) 37 | print(estrutura) 38 | s2.setsockopt(socket.IPPROTO_IP, MRT_ADD_VIF, estrutura) 39 | 40 | estrutura = struct.pack("HBBI 4s 4s", 1, 0x0, 1, 0, socket.inet_aton("192.168.2.2"), socket.inet_aton("0.0.0.0")) 41 | print(estrutura) 42 | s2.setsockopt(socket.IPPROTO_IP, MRT_ADD_VIF, estrutura) 43 | 44 | 45 | #time.sleep(5) 46 | 47 | while True: 48 | print("recv:") 49 | msg = s2.recv(5000) 50 | print(len(msg)) 51 | (_, _, im_msgtype, im_mbz, im_vif, _, im_src, im_dst, _) = struct.unpack("II B B B B 4s 4s 8s", msg) 52 | print(im_msgtype) 53 | print(im_mbz) 54 | print(im_vif) 55 | print(socket.inet_ntoa(im_src)) 56 | print(socket.inet_ntoa(im_dst)) 57 | if im_msgtype == IGMPMSG_NOCACHE: 58 | print("^^ IGMP NO CACHE") 59 | print(struct.unpack("II B B B B 4s 4s 8s", msg)) 60 | 61 | 62 | 63 | #s2.setsockopt(socket.IPPROTO_IP, MRT_PIM, 1) 64 | #print(s2.getsockopt(socket.IPPROTO_IP, 208)) 65 | #s2.setsockopt(socket.IPPROTO_IP, 208, 0) 66 | 67 | 68 | #ADD MULTICAST FORWARDING ENTRY 69 | estrutura = struct.pack("4s 4s H BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB IIIi", socket.inet_aton("10.0.0.2"), socket.inet_aton("224.1.1.113"), 0, 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 0) 70 | s2.setsockopt(socket.IPPROTO_IP, MRT_ADD_MFC, estrutura) 71 | 72 | time.sleep(30) 73 | 74 | #MRT DONE 75 | s2.setsockopt(socket.IPPROTO_IP, MRT_DONE, 1) 76 | s2.close() 77 | exit(1) 78 | -------------------------------------------------------------------------------- /config/config_example.yml: -------------------------------------------------------------------------------- 1 | MulticastVRF: 0 2 | UnicastVRF: 254 3 | 4 | PIM-DM: 5 | DefaultTimers: 6 | ASSERT_TIME: 180 7 | GRAFT_RETRY_PERIOD: 3 8 | JP_OVERRIDE_INTERVAL: 3.0 9 | OVERRIDE_INTERVAL: 2.5 10 | PROPAGATION_DELAY: 0.5 11 | REFRESH_INTERVAL: 60 12 | SOURCE_LIFETIME: 210 13 | T_LIMIT: 210 14 | Interfaces: 15 | eth0: 16 | ipv4: 17 | enabled: true 18 | state_refresh: true 19 | ipv6: 20 | enabled: true 21 | state_refresh: true 22 | eth1: 23 | ipv4: 24 | enabled: true 25 | state_refresh: true 26 | ipv6: 27 | enabled: true 28 | state_refresh: true 29 | eth2: 30 | ipv4: 31 | enabled: true 32 | state_refresh: true 33 | ipv6: 34 | enabled: true 35 | state_refresh: true 36 | 37 | IGMP: 38 | Settings: 39 | GROUP_MEMBERSHIP_INTERVAL: 260 40 | LAST_MEMBER_QUERY_COUNT: 2 41 | LAST_MEMBER_QUERY_INTERVAL: 1 42 | MAX_RESPONSE_TIME_LAST_MEMBER_QUERY_INTERVAL: 10 43 | MAX_RESPONSE_TIME_QUERY_RESPONSE_INTERVAL: 100 44 | OTHER_QUERIER_PRESENT_INTERVAL: 255.0 45 | QUERY_INTERVAL: 125 46 | QUERY_RESPONSE_INTERVAL: 10 47 | ROBUSTNESS_VARIABLE: 2 48 | STARTUP_QUERY_COUNT: 2 49 | STARTUP_QUERY_INTERVAL: 31.25 50 | UNSOLICITED_REPORT_INTERVAL: 10 51 | VERSION_1_ROUTER_PRESENT_TIMEOUT: 400 52 | Interfaces: 53 | eth0: 54 | enabled: true 55 | eth1: 56 | enabled: true 57 | eth2: 58 | enabled: true 59 | 60 | MLD: 61 | Settings: 62 | LAST_LISTENER_QUERY_COUNT: 2 63 | LAST_LISTENER_QUERY_INTERVAL: 1 64 | MULTICAST_LISTENER_INTERVAL: 260 65 | OTHER_QUERIER_PRESENT_INTERVAL: 255.0 66 | QUERY_INTERVAL: 125 67 | QUERY_RESPONSE_INTERVAL: 10 68 | ROBUSTNESS_VARIABLE: 2 69 | STARTUP_QUERY_COUNT: 2 70 | STARTUP_QUERY_INTERVAL: 31.25 71 | UNSOLICITED_REPORT_INTERVAL: 10 72 | Interfaces: 73 | eth0: 74 | enabled: true 75 | eth1: 76 | enabled: true 77 | eth2: 78 | enabled: true 79 | -------------------------------------------------------------------------------- /filter.h: -------------------------------------------------------------------------------- 1 | /* based on https://github.com/the-tcpdump-group/tcpdump */ 2 | 3 | #ifndef FILTER_H 4 | #define FILTER_H 5 | 6 | #define MAXIMUM_SNAPLEN 262144 7 | 8 | #include 9 | #include 10 | 11 | int filter_try_compile(struct bpf_program *fp, const char *cmdbuf) 12 | { 13 | int dump_dlt, ret; 14 | pcap_t *pd; 15 | 16 | dump_dlt = DLT_EN10MB; 17 | fprintf(stderr, "Warning: assuming Ethernet\n"); 18 | pd = pcap_open_dead(dump_dlt, MAXIMUM_SNAPLEN); 19 | 20 | ret = pcap_compile(pd, fp, cmdbuf, 1, 0); 21 | if (ret < 0) 22 | fprintf(stderr, "%s", pcap_geterr(pd)); 23 | 24 | pcap_close(pd); 25 | return (ret); 26 | } 27 | 28 | #endif /* FILTER_H */ 29 | -------------------------------------------------------------------------------- /pcap.pyx: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | import struct 4 | 5 | ctypedef unsigned int u_int 6 | ctypedef unsigned char u_char 7 | ctypedef unsigned short int u_short 8 | ctypedef u_int bpf_u_int32 9 | 10 | cdef extern from "pcap.h": 11 | struct bpf_insn: 12 | u_short code 13 | u_char jt 14 | u_char jf 15 | bpf_u_int32 k 16 | struct bpf_program: 17 | bpf_insn *bf_insns 18 | u_int bf_len 19 | 20 | cdef extern from "pcap.h": 21 | void pcap_freecode(bpf_program *fp) 22 | 23 | cdef extern from "filter.h": 24 | int filter_try_compile(bpf_program *fp, const char *cmdbuf) 25 | 26 | 27 | cdef class bpf: 28 | """bpf(filter) -> BPF filter object""" 29 | 30 | cdef bpf_program fcode 31 | 32 | def __init__(self, char *filter): 33 | if filter_try_compile(&self.fcode, filter) < 0: 34 | raise IOError, 'bad filter' 35 | 36 | def compiled_filter(self): 37 | cdef bpf_insn *bf_insns 38 | bf_insns = self.fcode.bf_insns 39 | size = self.fcode.bf_len 40 | 41 | return size, b''.join( 42 | struct.pack('HBBI', bf_insns[i].code, bf_insns[i].jt, bf_insns[i].jf, bf_insns[i].k) 43 | for i in range(size) 44 | ) 45 | 46 | def dump(self): 47 | cdef bpf_insn *bf_insns 48 | cdef bpf_insn bf_insn 49 | bf_insns = self.fcode.bf_insns 50 | for i in range(self.fcode.bf_len): 51 | bf_insn = bf_insns[i] 52 | print("{ 0x%x, %d, %d, 0x%08x }," % (bf_insn.code, bf_insn.jt, bf_insn.jf, bf_insn.k)) 53 | 54 | def __dealloc__(self): 55 | pcap_freecode(&self.fcode) 56 | -------------------------------------------------------------------------------- /pimdm/Config.py: -------------------------------------------------------------------------------- 1 | import sys, yaml 2 | from functools import partial 3 | from pimdm.tree import pim_globals 4 | from igmp.igmp2 import igmp_globals 5 | from mld.mld1 import mld_globals 6 | from pimdm import Main 7 | 8 | 9 | def parse_config_file(file_path): 10 | """ 11 | Parse yaml file and set everything on protocol process accordingly 12 | """ 13 | with open(file_path) as f: 14 | data = yaml.load(f, Loader=yaml.FullLoader) 15 | print(data) 16 | 17 | print(type(data.get("UnicastVRF", 254))) 18 | 19 | multicast_vrf = data.get("MulticastVRF", 0) 20 | pim_globals.MULTICAST_TABLE_ID = multicast_vrf 21 | pim_globals.UNICAST_TABLE_ID = data.get("UnicastVRF", 254) 22 | pim_config = data.get("PIM-DM", {}) 23 | igmp_config = data.get("IGMP", {}) 24 | mld_config = data.get("MLD", {}) 25 | 26 | ##### PIM config ###### 27 | if "DefaultTimers" in pim_config: 28 | pim_globals.ASSERT_TIME = pim_config["DefaultTimers"].get("ASSERT_TIME", pim_globals.ASSERT_TIME) 29 | pim_globals.GRAFT_RETRY_PERIOD = pim_config["DefaultTimers"].get("GRAFT_RETRY_PERIOD", pim_globals.GRAFT_RETRY_PERIOD) 30 | pim_globals.JP_OVERRIDE_INTERVAL = pim_config["DefaultTimers"].get("JP_OVERRIDE_INTERVAL", pim_globals.JP_OVERRIDE_INTERVAL) 31 | pim_globals.OVERRIDE_INTERVAL = pim_config["DefaultTimers"].get("OVERRIDE_INTERVAL", pim_globals.OVERRIDE_INTERVAL) 32 | pim_globals.PROPAGATION_DELAY = pim_config["DefaultTimers"].get("PROPAGATION_DELAY", pim_globals.PROPAGATION_DELAY) 33 | pim_globals.REFRESH_INTERVAL = pim_config["DefaultTimers"].get("REFRESH_INTERVAL", pim_globals.REFRESH_INTERVAL) 34 | pim_globals.SOURCE_LIFETIME = pim_config["DefaultTimers"].get("SOURCE_LIFETIME", pim_globals.SOURCE_LIFETIME) 35 | pim_globals.T_LIMIT = pim_config["DefaultTimers"].get("T_LIMIT", pim_globals.T_LIMIT) 36 | 37 | if "Interfaces" in pim_config: 38 | interface_dict = pim_config["Interfaces"] 39 | add_pim_interface_dict = { 40 | 'ipv4': partial(Main.add_pim_interface, ipv4=True, ipv6=False), 41 | 'ipv6': partial(Main.add_pim_interface, ipv4=False, ipv6=True), 42 | } 43 | 44 | for if_name, ip_family_dict in interface_dict.items(): 45 | for ip_family, if_dict in ip_family_dict.items(): 46 | if if_dict.get("enabled", False): 47 | try: 48 | add_pim_interface_dict[ip_family]( 49 | interface_name=if_name, 50 | state_refresh_capable=if_dict.get("state_refresh", False), 51 | ) 52 | except Exception as e: 53 | print(e, file=sys.stderr) 54 | 55 | 56 | ##### IGMP config ####### 57 | if "Settings" in igmp_config: 58 | igmp_globals.ROBUSTNESS_VARIABLE = igmp_config["Settings"].get("ROBUSTNESS_VARIABLE", igmp_globals.ROBUSTNESS_VARIABLE) 59 | igmp_globals.QUERY_INTERVAL = igmp_config["Settings"].get("QUERY_INTERVAL", igmp_globals.QUERY_INTERVAL) 60 | igmp_globals.QUERY_RESPONSE_INTERVAL = igmp_config["Settings"].get("QUERY_RESPONSE_INTERVAL", igmp_globals.QUERY_RESPONSE_INTERVAL) 61 | igmp_globals.MAX_RESPONSE_TIME_QUERY_RESPONSE_INTERVAL = igmp_config["Settings"].get("MAX_RESPONSE_TIME_QUERY_RESPONSE_INTERVAL", igmp_globals.QUERY_RESPONSE_INTERVAL*10) 62 | igmp_globals.GROUP_MEMBERSHIP_INTERVAL = igmp_config["Settings"].get("GROUP_MEMBERSHIP_INTERVAL", igmp_globals.ROBUSTNESS_VARIABLE * igmp_globals.QUERY_INTERVAL + igmp_globals.QUERY_RESPONSE_INTERVAL) 63 | igmp_globals.OTHER_QUERIER_PRESENT_INTERVAL = igmp_config["Settings"].get("OTHER_QUERIER_PRESENT_INTERVAL", igmp_globals.ROBUSTNESS_VARIABLE * igmp_globals.QUERY_INTERVAL + igmp_globals.QUERY_RESPONSE_INTERVAL / 2) 64 | igmp_globals.STARTUP_QUERY_INTERVAL = igmp_config["Settings"].get("STARTUP_QUERY_INTERVAL", igmp_globals.QUERY_INTERVAL / 4) 65 | igmp_globals.STARTUP_QUERY_COUNT = igmp_config["Settings"].get("STARTUP_QUERY_COUNT", igmp_globals.ROBUSTNESS_VARIABLE) 66 | igmp_globals.LAST_MEMBER_QUERY_INTERVAL = igmp_config["Settings"].get("LAST_MEMBER_QUERY_INTERVAL", igmp_globals.LAST_MEMBER_QUERY_INTERVAL) 67 | igmp_globals.MAX_RESPONSE_TIME_LAST_MEMBER_QUERY_INTERVAL = igmp_config["Settings"].get("LAST_MEMBER_QUERY_COUNT", igmp_globals.LAST_MEMBER_QUERY_INTERVAL * 10) 68 | igmp_globals.LAST_MEMBER_QUERY_COUNT = igmp_config["Settings"].get("LAST_MEMBER_QUERY_COUNT", igmp_globals.ROBUSTNESS_VARIABLE) 69 | igmp_globals.UNSOLICITED_REPORT_INTERVAL = igmp_config["Settings"].get("UNSOLICITED_REPORT_INTERVAL", igmp_globals.UNSOLICITED_REPORT_INTERVAL) 70 | igmp_globals.VERSION_1_ROUTER_PRESENT_TIMEOUT = igmp_config["Settings"].get("VERSION_1_ROUTER_PRESENT_TIMEOUT", igmp_globals.VERSION_1_ROUTER_PRESENT_TIMEOUT) 71 | 72 | if "Interfaces" in igmp_config: 73 | interface_dict = igmp_config["Interfaces"] 74 | 75 | for if_name, if_value in interface_dict.items(): 76 | try: 77 | if if_value.get("enabled", False): 78 | Main.add_membership_interface(interface_name=if_name, ipv4=True, ipv6=False) 79 | except Exception as e: 80 | print(e, file=sys.stderr) 81 | 82 | ##### MLD config ####### 83 | if "Settings" in mld_config: 84 | mld_globals.ROBUSTNESS_VARIABLE = mld_config["Settings"].get("ROBUSTNESS_VARIABLE", mld_globals.ROBUSTNESS_VARIABLE) 85 | mld_globals.QUERY_INTERVAL = mld_config["Settings"].get("QUERY_INTERVAL", mld_globals.QUERY_INTERVAL) 86 | mld_globals.QUERY_RESPONSE_INTERVAL = mld_config["Settings"].get("QUERY_RESPONSE_INTERVAL", mld_globals.QUERY_RESPONSE_INTERVAL) 87 | mld_globals.MULTICAST_LISTENER_INTERVAL = mld_config["Settings"].get("MULTICAST_LISTENER_INTERVAL", (mld_globals.ROBUSTNESS_VARIABLE * mld_globals.QUERY_INTERVAL) + (mld_globals.QUERY_RESPONSE_INTERVAL)) 88 | mld_globals.OTHER_QUERIER_PRESENT_INTERVAL = mld_config["Settings"].get("OTHER_QUERIER_PRESENT_INTERVAL", (mld_globals.ROBUSTNESS_VARIABLE * mld_globals.QUERY_INTERVAL) + 0.5 * mld_globals.QUERY_RESPONSE_INTERVAL) 89 | mld_globals.STARTUP_QUERY_INTERVAL = mld_config["Settings"].get("STARTUP_QUERY_INTERVAL", (1 / 4) * mld_globals.QUERY_INTERVAL) 90 | mld_globals.STARTUP_QUERY_COUNT = mld_config["Settings"].get("STARTUP_QUERY_COUNT", mld_globals.ROBUSTNESS_VARIABLE) 91 | mld_globals.LAST_LISTENER_QUERY_INTERVAL = mld_config["Settings"].get("LAST_LISTENER_QUERY_INTERVAL", mld_globals.LAST_LISTENER_QUERY_INTERVAL) 92 | mld_globals.LAST_LISTENER_QUERY_COUNT = mld_config["Settings"].get("LAST_LISTENER_QUERY_COUNT", mld_globals.ROBUSTNESS_VARIABLE) 93 | mld_globals.UNSOLICITED_REPORT_INTERVAL = mld_config["Settings"].get("UNSOLICITED_REPORT_INTERVAL", mld_globals.UNSOLICITED_REPORT_INTERVAL) 94 | 95 | if "Interfaces" in mld_config: 96 | interface_dict = mld_config["Interfaces"] 97 | 98 | for if_name, if_value in interface_dict.items(): 99 | try: 100 | if if_value.get("enabled", False): 101 | Main.add_membership_interface(interface_name=if_name, ipv4=False, ipv6=True) 102 | except Exception as e: 103 | print(e, file=sys.stderr) 104 | 105 | 106 | def get_yaml_file(): 107 | """ 108 | Get configuration file from live protocol process 109 | """ 110 | dict_file = { 111 | 'MulticastVRF': pim_globals.MULTICAST_TABLE_ID, 112 | 'UnicastVRF': pim_globals.UNICAST_TABLE_ID, 113 | 'PIM-DM': { 114 | "DefaultTimers": { 115 | "ASSERT_TIME": pim_globals.ASSERT_TIME, 116 | "GRAFT_RETRY_PERIOD": pim_globals.GRAFT_RETRY_PERIOD, 117 | "JP_OVERRIDE_INTERVAL": pim_globals.JP_OVERRIDE_INTERVAL, 118 | "OVERRIDE_INTERVAL": pim_globals.OVERRIDE_INTERVAL, 119 | "PROPAGATION_DELAY": pim_globals.PROPAGATION_DELAY, 120 | "REFRESH_INTERVAL": pim_globals.REFRESH_INTERVAL, 121 | "SOURCE_LIFETIME": pim_globals.SOURCE_LIFETIME, 122 | "T_LIMIT": pim_globals.T_LIMIT, 123 | }, 124 | "Interfaces": {}, 125 | }, 126 | 'IGMP': { 127 | "Settings": { 128 | "ROBUSTNESS_VARIABLE": igmp_globals.ROBUSTNESS_VARIABLE, 129 | "QUERY_INTERVAL": igmp_globals.QUERY_INTERVAL, 130 | "QUERY_RESPONSE_INTERVAL": igmp_globals.QUERY_RESPONSE_INTERVAL, 131 | "MAX_RESPONSE_TIME_QUERY_RESPONSE_INTERVAL": igmp_globals.MAX_RESPONSE_TIME_QUERY_RESPONSE_INTERVAL, 132 | "GROUP_MEMBERSHIP_INTERVAL": igmp_globals.GROUP_MEMBERSHIP_INTERVAL, 133 | "OTHER_QUERIER_PRESENT_INTERVAL": igmp_globals.OTHER_QUERIER_PRESENT_INTERVAL, 134 | "STARTUP_QUERY_INTERVAL": igmp_globals.STARTUP_QUERY_INTERVAL, 135 | "STARTUP_QUERY_COUNT": igmp_globals.STARTUP_QUERY_COUNT, 136 | "LAST_MEMBER_QUERY_INTERVAL": igmp_globals.LAST_MEMBER_QUERY_INTERVAL, 137 | "MAX_RESPONSE_TIME_LAST_MEMBER_QUERY_INTERVAL": igmp_globals.MAX_RESPONSE_TIME_LAST_MEMBER_QUERY_INTERVAL, 138 | "LAST_MEMBER_QUERY_COUNT": igmp_globals.LAST_MEMBER_QUERY_COUNT, 139 | "UNSOLICITED_REPORT_INTERVAL": igmp_globals.UNSOLICITED_REPORT_INTERVAL, 140 | "VERSION_1_ROUTER_PRESENT_TIMEOUT": igmp_globals.VERSION_1_ROUTER_PRESENT_TIMEOUT, 141 | }, 142 | "Interfaces": {}, 143 | }, 144 | 'MLD': { 145 | "Settings": { 146 | "ROBUSTNESS_VARIABLE": mld_globals.ROBUSTNESS_VARIABLE, 147 | "QUERY_INTERVAL": mld_globals.QUERY_INTERVAL, 148 | "QUERY_RESPONSE_INTERVAL": mld_globals.QUERY_RESPONSE_INTERVAL, 149 | "MULTICAST_LISTENER_INTERVAL": mld_globals.MULTICAST_LISTENER_INTERVAL, 150 | "OTHER_QUERIER_PRESENT_INTERVAL": mld_globals.OTHER_QUERIER_PRESENT_INTERVAL, 151 | "STARTUP_QUERY_INTERVAL": mld_globals.STARTUP_QUERY_INTERVAL, 152 | "STARTUP_QUERY_COUNT": mld_globals.STARTUP_QUERY_COUNT, 153 | "LAST_LISTENER_QUERY_INTERVAL": mld_globals.LAST_LISTENER_QUERY_INTERVAL, 154 | "LAST_LISTENER_QUERY_COUNT": mld_globals.LAST_LISTENER_QUERY_COUNT, 155 | "UNSOLICITED_REPORT_INTERVAL": mld_globals.UNSOLICITED_REPORT_INTERVAL, 156 | }, 157 | "Interfaces": {}, 158 | } 159 | } 160 | 161 | for if_name, if_value in Main.interfaces.items(): 162 | dict_file["PIM-DM"]["Interfaces"][if_name] = {} 163 | dict_file["PIM-DM"]["Interfaces"][if_name]["ipv4"] = { 164 | "enabled": True, 165 | } 166 | 167 | for if_name, if_value in Main.interfaces_v6.items(): 168 | if if_name not in dict_file["PIM-DM"]["Interfaces"]: 169 | dict_file["PIM-DM"]["Interfaces"][if_name] = {} 170 | 171 | dict_file["PIM-DM"]["Interfaces"][if_name]["ipv6"] = { 172 | "enabled": True, 173 | } 174 | 175 | for if_name in Main.igmp_interfaces.keys(): 176 | dict_file["IGMP"]["Interfaces"][if_name] = { 177 | "enabled": True, 178 | } 179 | 180 | for if_name in Main.mld_interfaces.keys(): 181 | dict_file["MLD"]["Interfaces"][if_name] = { 182 | "enabled": True, 183 | } 184 | 185 | return yaml.dump(dict_file) 186 | 187 | 188 | def get_vrfs(file_path): 189 | """ 190 | Get vrf configuration from yaml file. 191 | This is only used by Run.py to create the correct daemons accordingly (daemons are bound to specific VRFs). 192 | """ 193 | with open(file_path) as f: 194 | data = yaml.load(f, Loader=yaml.FullLoader) 195 | multicast_vrf = data.get("MulticastVRF", 0) 196 | unicast_vrf = data.get("UnicastVRF", 254) 197 | return [multicast_vrf, unicast_vrf] 198 | -------------------------------------------------------------------------------- /pimdm/Interface.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from abc import ABCMeta, abstractmethod 3 | import threading 4 | import traceback 5 | 6 | 7 | class Interface(metaclass=ABCMeta): 8 | MCAST_GRP = '224.0.0.13' 9 | 10 | def __init__(self, interface_name, recv_socket, send_socket, vif_index): 11 | self.interface_name = interface_name 12 | 13 | # virtual interface index for the multicast routing table 14 | self.vif_index = vif_index 15 | 16 | # set receive socket and send socket 17 | self._send_socket = send_socket 18 | self._recv_socket = recv_socket 19 | self.interface_enabled = False 20 | 21 | self.drop_packet_type = None 22 | 23 | def _enable(self): 24 | """ 25 | Enable this interface 26 | This will start a thread to be executed in the background to be used in the reception of control packets 27 | """ 28 | self.interface_enabled = True 29 | # run receive method in background 30 | receive_thread = threading.Thread(target=self.receive) 31 | receive_thread.daemon = True 32 | receive_thread.start() 33 | 34 | def receive(self): 35 | """ 36 | Method that will be executed in the background for the reception of control packets 37 | """ 38 | while self.interface_enabled: 39 | try: 40 | (raw_bytes, ancdata, _, src_addr) = self._recv_socket.recvmsg(256 * 1024, 500) 41 | if raw_bytes: 42 | self._receive(raw_bytes, ancdata, src_addr) 43 | except Exception: 44 | traceback.print_exc() 45 | continue 46 | 47 | @abstractmethod 48 | def _receive(self, raw_bytes, ancdata, src_addr): 49 | """ 50 | Subclass method to be implemented 51 | This method will be invoked whenever a new control packet is received 52 | """ 53 | raise NotImplementedError 54 | 55 | def send(self, data: bytes, group_ip: str): 56 | """ 57 | Send a control packet through this interface 58 | Explicitly destined to group_ip (can be unicast or multicast IP) 59 | """ 60 | if self.interface_enabled and data: 61 | self._send_socket.sendto(data, (group_ip, 0)) 62 | 63 | def remove(self): 64 | """ 65 | This interface is no longer active.... 66 | Clear all state regarding it 67 | """ 68 | self.interface_enabled = False 69 | try: 70 | self._recv_socket.shutdown(socket.SHUT_RDWR) 71 | except Exception: 72 | pass 73 | self._recv_socket.close() 74 | self._send_socket.close() 75 | 76 | def is_enabled(self): 77 | """ 78 | Verify if this interface is enabled 79 | """ 80 | return self.interface_enabled 81 | 82 | @abstractmethod 83 | def get_ip(self): 84 | """ 85 | Get IP of this interface 86 | """ 87 | raise NotImplementedError 88 | -------------------------------------------------------------------------------- /pimdm/InterfacePIM6.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import random 3 | import struct 4 | import logging 5 | import ipaddress 6 | import netifaces 7 | from pimdm import Main 8 | from socket import if_nametoindex 9 | from pimdm.Interface import Interface 10 | from .InterfacePIM import InterfacePim 11 | from pimdm.rwlock.RWLock import RWLockWrite 12 | from pimdm.packet.ReceivedPacket import ReceivedPacket_v6 13 | 14 | 15 | class InterfacePim6(InterfacePim): 16 | MCAST_GRP = "ff02::d" 17 | 18 | def __init__(self, interface_name: str, vif_index:int, state_refresh_capable:bool=False): 19 | # generation id 20 | self.generation_id = random.getrandbits(32) 21 | 22 | # When PIM is enabled on an interface or when a router first starts, the Hello Timer (HT) 23 | # MUST be set to random value between 0 and Triggered_Hello_Delay 24 | self.hello_timer = None 25 | 26 | # state refresh capable 27 | self._state_refresh_capable = state_refresh_capable 28 | self._neighbors_state_refresh_capable = False 29 | 30 | # todo: lan delay enabled 31 | self._lan_delay_enabled = False 32 | 33 | # todo: propagation delay 34 | self._propagation_delay = self.PROPAGATION_DELAY 35 | 36 | # todo: override interval 37 | self._override_interval = self.OVERRIDE_INTERNAL 38 | 39 | # pim neighbors 40 | self._had_neighbors = False 41 | self.neighbors = {} 42 | self.neighbors_lock = RWLockWrite() 43 | self.interface_logger = logging.LoggerAdapter(InterfacePim.LOGGER, {'vif': vif_index, 'interfacename': interface_name}) 44 | 45 | # SOCKET 46 | s = socket.socket(socket.AF_INET6, socket.SOCK_RAW, socket.IPPROTO_PIM) 47 | 48 | ip_interface = "" 49 | if_addr_dict = netifaces.ifaddresses(interface_name) 50 | if not netifaces.AF_INET6 in if_addr_dict: 51 | raise Exception("Adding PIM interface failed because %s does not " 52 | "have any ipv6 address" % interface_name) 53 | for network_dict in if_addr_dict[netifaces.AF_INET6]: 54 | full_ip_interface = network_dict["addr"] 55 | ip_interface = full_ip_interface.split("%")[0] 56 | if ipaddress.IPv6Address(ip_interface).is_link_local: 57 | # bind to interface 58 | s.bind(socket.getaddrinfo(full_ip_interface, None, 0, socket.SOCK_RAW, 0, socket.AI_PASSIVE)[0][4]) 59 | break 60 | 61 | self.ip_interface = ip_interface 62 | 63 | # allow other sockets to bind this port too 64 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 65 | 66 | # explicitly join the multicast group on the interface specified 67 | if_index = if_nametoindex(interface_name) 68 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, 69 | socket.inet_pton(socket.AF_INET6, InterfacePim6.MCAST_GRP) + struct.pack('@I', if_index)) 70 | s.setsockopt(socket.SOL_SOCKET, 25, str(interface_name + '\0').encode('utf-8')) 71 | 72 | # set socket output interface 73 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, struct.pack('@I', if_index)) 74 | 75 | # set socket TTL to 1 76 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, 1) 77 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_UNICAST_HOPS, 1) 78 | 79 | # don't receive outgoing packets 80 | s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_LOOP, 0) 81 | Interface.__init__(self, interface_name, s, s, vif_index) 82 | Interface._enable(self) 83 | self.force_send_hello() 84 | 85 | @staticmethod 86 | def get_kernel(): 87 | return Main.kernel_v6 88 | 89 | def send(self, data: bytes, group_ip: str=MCAST_GRP): 90 | super().send(data=data, group_ip=group_ip) 91 | 92 | def _receive(self, raw_bytes, ancdata, src_addr): 93 | if raw_bytes: 94 | packet = ReceivedPacket_v6(raw_bytes, ancdata, src_addr, 103, self) 95 | self.PKT_FUNCTIONS[packet.payload.get_pim_type()](self, packet) 96 | -------------------------------------------------------------------------------- /pimdm/Main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import netifaces 5 | import logging 6 | import logging.handlers 7 | from prettytable import PrettyTable 8 | from pimdm.tree import pim_globals 9 | 10 | from pimdm import UnicastRouting 11 | from pimdm.TestLogger import RootFilter 12 | 13 | interfaces = {} # interfaces with multicast routing enabled 14 | igmp_interfaces = {} # igmp interfaces 15 | interfaces_v6 = {} # pim v6 interfaces 16 | mld_interfaces = {} # mld interfaces 17 | kernel = None 18 | kernel_v6 = None 19 | unicast_routing = None 20 | logger = None 21 | 22 | 23 | def add_pim_interface(interface_name, state_refresh_capable: bool = False, ipv4=True, ipv6=False): 24 | if interface_name == "*": 25 | for interface_name in netifaces.interfaces(): 26 | add_pim_interface(interface_name, ipv4, ipv6) 27 | return 28 | 29 | if ipv4 and kernel is not None: 30 | kernel.create_pim_interface(interface_name=interface_name, state_refresh_capable=state_refresh_capable) 31 | if ipv6 and kernel_v6 is not None: 32 | kernel_v6.create_pim_interface(interface_name=interface_name, state_refresh_capable=state_refresh_capable) 33 | 34 | 35 | def add_membership_interface(interface_name, ipv4=True, ipv6=False): 36 | if interface_name == "*": 37 | for interface_name in netifaces.interfaces(): 38 | add_membership_interface(interface_name, ipv4, ipv6) 39 | return 40 | 41 | try: 42 | if ipv4 and kernel is not None: 43 | kernel.create_membership_interface(interface_name=interface_name) 44 | if ipv6 and kernel_v6 is not None: 45 | kernel_v6.create_membership_interface(interface_name=interface_name) 46 | except Exception as e: 47 | e.args = ( 48 | 'Failed to create membership interface for %s\n%s' % (interface_name, e.args[0]), 49 | *e.args[1:], 50 | ) 51 | raise e 52 | 53 | 54 | def remove_interface(interface_name, pim=False, membership=False, ipv4=True, ipv6=False): 55 | if interface_name == "*": 56 | for interface_name in netifaces.interfaces(): 57 | remove_interface(interface_name, pim, membership, ipv4, ipv6) 58 | return 59 | 60 | if ipv4 and kernel is not None: 61 | kernel.remove_interface(interface_name, pim=pim, membership=membership) 62 | if ipv6 and kernel_v6 is not None: 63 | kernel_v6.remove_interface(interface_name, pim=pim, membership=membership) 64 | 65 | 66 | def list_neighbors(ipv4=False, ipv6=False): 67 | if ipv4: 68 | interfaces_list = interfaces.values() 69 | elif ipv6: 70 | interfaces_list = interfaces_v6.values() 71 | else: 72 | return "Unknown IP family" 73 | 74 | t = PrettyTable(['Interface', 'Neighbor IP', 'Hello Hold Time', "Generation ID", "Uptime"]) 75 | check_time = time.time() 76 | for interface in interfaces_list: 77 | for neighbor in interface.get_neighbors(): 78 | uptime = check_time - neighbor.time_of_last_update 79 | uptime = 0 if (uptime < 0) else uptime 80 | 81 | t.add_row( 82 | [interface.interface_name, neighbor.ip, neighbor.hello_hold_time, neighbor.generation_id, time.strftime("%H:%M:%S", time.gmtime(uptime))]) 83 | print(t) 84 | return str(t) 85 | 86 | 87 | def list_enabled_interfaces(ipv4=False, ipv6=False): 88 | if ipv4: 89 | t = PrettyTable(['Interface', 'IP', 'PIM/IGMP Enabled', 'State Refresh Enabled', 'IGMP State']) 90 | family = netifaces.AF_INET 91 | pim_interfaces = interfaces 92 | membership_interfaces = igmp_interfaces 93 | elif ipv6: 94 | t = PrettyTable(['Interface', 'IP', 'PIM/MLD Enabled', 'State Refresh Enabled', 'MLD State']) 95 | family = netifaces.AF_INET6 96 | pim_interfaces = interfaces_v6 97 | membership_interfaces = mld_interfaces 98 | else: 99 | return "Unknown IP family" 100 | 101 | for interface in netifaces.interfaces(): 102 | try: 103 | # TODO: fix same interface with multiple ips 104 | ip = netifaces.ifaddresses(interface)[family][0]['addr'] 105 | pim_enabled = interface in pim_interfaces 106 | membership_enabled = interface in membership_interfaces 107 | enabled = str(pim_enabled) + "/" + str(membership_enabled) 108 | state_refresh_enabled = "-" 109 | if pim_enabled: 110 | state_refresh_enabled = pim_interfaces[interface].is_state_refresh_enabled() 111 | membership_state = "-" 112 | if membership_enabled: 113 | membership_state = membership_interfaces[interface].interface_state.print_state() 114 | t.add_row([interface, ip, enabled, state_refresh_enabled, membership_state]) 115 | except Exception: 116 | continue 117 | print(t) 118 | return str(t) 119 | 120 | 121 | def list_state(ipv4=True, ipv6=False): 122 | state_text = "" 123 | if ipv4: 124 | state_text = "IGMP State:\n{}\n\n\n\nMulticast Routing State:\n{}" 125 | elif ipv6: 126 | state_text = "MLD State:\n{}\n\n\n\nMulticast Routing State:\n{}" 127 | else: 128 | return state_text 129 | return state_text.format(list_membership_state(ipv4, ipv6), list_routing_state(ipv4, ipv6)) 130 | 131 | 132 | def list_membership_state(ipv4=True, ipv6=False): 133 | t = PrettyTable(['Interface', 'RouterState', 'Group Adress', 'GroupState']) 134 | if ipv4: 135 | membership_interfaces = igmp_interfaces 136 | elif ipv6: 137 | membership_interfaces = mld_interfaces 138 | else: 139 | membership_interfaces = {} 140 | 141 | for (interface_name, interface_obj) in list(membership_interfaces.items()): 142 | interface_state = interface_obj.interface_state 143 | state_txt = interface_state.print_state() 144 | print(interface_state.group_state.items()) 145 | 146 | for (group_addr, group_state) in list(interface_state.group_state.items()): 147 | print(group_addr) 148 | group_state_txt = group_state.print_state() 149 | t.add_row([interface_name, state_txt, group_addr, group_state_txt]) 150 | return str(t) 151 | 152 | 153 | def list_routing_state(ipv4=False, ipv6=False): 154 | if ipv4: 155 | routes = kernel.routing.values() 156 | vif_indexes = kernel.vif_index_to_name_dic.keys() 157 | dict_index_to_name = kernel.vif_index_to_name_dic 158 | elif ipv6: 159 | routes = kernel_v6.routing.values() 160 | vif_indexes = kernel_v6.vif_index_to_name_dic.keys() 161 | dict_index_to_name = kernel_v6.vif_index_to_name_dic 162 | else: 163 | raise Exception("Unknown IP family") 164 | 165 | routing_entries = [] 166 | for a in list(routes): 167 | for b in list(a.values()): 168 | routing_entries.append(b) 169 | 170 | t = PrettyTable(['SourceIP', 'GroupIP', 'Interface', 'OriginatorState', 'PruneState', 'AssertState', 'LocalMembership', "Is Forwarding?"]) 171 | for entry in routing_entries: 172 | ip = entry.source_ip 173 | group = entry.group_ip 174 | upstream_if_index = entry.inbound_interface_index 175 | 176 | for index in vif_indexes: 177 | interface_state = entry.interface_state[index] 178 | interface_name = dict_index_to_name[index] 179 | local_membership = type(interface_state._local_membership_state).__name__ 180 | try: 181 | assert_state = type(interface_state._assert_state).__name__ 182 | if index != upstream_if_index: 183 | prune_state = type(interface_state._prune_state).__name__ 184 | originator_state = "-" 185 | is_forwarding = interface_state.is_forwarding() 186 | else: 187 | prune_state = type(interface_state._graft_prune_state).__name__ 188 | is_forwarding = "upstream" 189 | originator_state = type(interface_state._originator_state).__name__ 190 | except: 191 | originator_state = "-" 192 | prune_state = "-" 193 | assert_state = "-" 194 | is_forwarding = "-" 195 | 196 | t.add_row([ip, group, interface_name, originator_state, prune_state, assert_state, local_membership, is_forwarding]) 197 | return str(t) 198 | 199 | 200 | def list_instances(): 201 | """ 202 | List instance information 203 | """ 204 | t = "{}|{}|{}" 205 | return t.format(os.getpid(), pim_globals.MULTICAST_TABLE_ID, pim_globals.UNICAST_TABLE_ID) 206 | 207 | 208 | def stop(): 209 | remove_interface("*", pim=True, membership=True, ipv4=True, ipv6=True) 210 | if kernel is not None: 211 | kernel.exit() 212 | if kernel_v6 is not None: 213 | kernel_v6.exit() 214 | unicast_routing.stop() 215 | 216 | 217 | def test(router_name, server_logger_ip): 218 | global logger 219 | socketHandler = logging.handlers.SocketHandler(server_logger_ip, 220 | logging.handlers.DEFAULT_TCP_LOGGING_PORT) 221 | # don't bother with a formatter, since a socket handler sends the event as 222 | # an unformatted pickle 223 | socketHandler.addFilter(RootFilter(router_name)) 224 | logger.addHandler(socketHandler) 225 | 226 | 227 | def get_config(): 228 | """ 229 | Get live configuration of PIM-DM process 230 | """ 231 | try: 232 | from . import Config 233 | return Config.get_yaml_file() 234 | except ModuleNotFoundError: 235 | return "PYYAML needs to be installed. Execute \"pip3 install pyyaml\"" 236 | except ImportError: 237 | return "PYYAML needs to be installed. Execute \"pip3 install pyyaml\"" 238 | 239 | 240 | def set_config(file_path): 241 | """ 242 | Set configuration of PIM-DM process 243 | """ 244 | from . import Config 245 | try: 246 | Config.parse_config_file(file_path) 247 | except: 248 | import traceback 249 | traceback.print_exc() 250 | 251 | 252 | def drop(interface_name, packet_type): 253 | interfaces.get(interface_name).drop_packet_type = packet_type 254 | 255 | 256 | def enable_ipv6_kernel(): 257 | """ 258 | Function to explicitly enable IPv6 Multicast Routing stack. 259 | This may not be enabled by default due to some old linux kernels that may not have IPv6 stack or do not have 260 | IPv6 multicast routing support 261 | """ 262 | global kernel_v6 263 | from pimdm.Kernel import Kernel6 264 | kernel_v6 = Kernel6() 265 | 266 | global interfaces_v6 267 | global mld_interfaces 268 | interfaces_v6 = kernel_v6.pim_interface 269 | mld_interfaces = kernel_v6.membership_interface 270 | 271 | 272 | def main(): 273 | # logging 274 | global logger 275 | logger = logging.getLogger('pim') 276 | mld_logger = logging.getLogger('mld') 277 | igmp_logger = logging.getLogger('igmp') 278 | logger.setLevel(logging.DEBUG) 279 | igmp_logger.setLevel(logging.DEBUG) 280 | mld_logger.setLevel(logging.DEBUG) 281 | handler = logging.StreamHandler(sys.stdout) 282 | handler.addFilter(RootFilter("")) 283 | handler.setLevel(logging.DEBUG) 284 | handler.setFormatter(logging.Formatter('%(asctime)-20s %(name)-50s %(tree)-35s %(vif)-2s %(interfacename)-5s ' 285 | '%(routername)-2s %(message)s')) 286 | logger.addHandler(handler) 287 | igmp_logger.addHandler(handler) 288 | mld_logger.addHandler(handler) 289 | 290 | global kernel 291 | from pimdm.Kernel import Kernel4 292 | kernel = Kernel4() 293 | 294 | global unicast_routing 295 | unicast_routing = UnicastRouting.UnicastRouting() 296 | 297 | global interfaces 298 | global igmp_interfaces 299 | interfaces = kernel.pim_interface 300 | igmp_interfaces = kernel.membership_interface 301 | 302 | try: 303 | enable_ipv6_kernel() 304 | except: 305 | pass 306 | -------------------------------------------------------------------------------- /pimdm/Neighbor.py: -------------------------------------------------------------------------------- 1 | import time 2 | import logging 3 | from threading import Timer 4 | from threading import Lock, RLock 5 | 6 | from pimdm.tree.pim_globals import HELLO_HOLD_TIME_NO_TIMEOUT, HELLO_HOLD_TIME_TIMEOUT 7 | from pimdm.utils import TYPE_CHECKING 8 | if TYPE_CHECKING: 9 | from pimdm.InterfacePIM import InterfacePim 10 | 11 | 12 | class Neighbor: 13 | LOGGER = logging.getLogger('pim.Interface.Neighbor') 14 | 15 | def __init__(self, contact_interface: "InterfacePim", ip, generation_id: int, hello_hold_time: int, 16 | state_refresh_capable: bool): 17 | if hello_hold_time == HELLO_HOLD_TIME_TIMEOUT: 18 | raise Exception 19 | logger_info = dict(contact_interface.interface_logger.extra) 20 | logger_info['neighbor_ip'] = ip 21 | self.neighbor_logger = logging.LoggerAdapter(self.LOGGER, logger_info) 22 | self.neighbor_logger.debug('Monitoring new neighbor ' + ip + ' with GenerationID: ' + str(generation_id) + 23 | '; HelloHoldTime: ' + str(hello_hold_time) + '; StateRefreshCapable: ' + 24 | str(state_refresh_capable)) 25 | self.contact_interface = contact_interface 26 | self.ip = ip 27 | self.generation_id = generation_id 28 | # todo lan prune delay 29 | # todo override interval 30 | self.state_refresh_capable = state_refresh_capable 31 | 32 | self.neighbor_liveness_timer = None 33 | self.hello_hold_time = None 34 | self.set_hello_hold_time(hello_hold_time) 35 | self.time_of_last_update = time.time() 36 | self.neighbor_lock = Lock() 37 | 38 | self.tree_interface_nlt_subscribers = [] 39 | self.tree_interface_nlt_subscribers_lock = RLock() 40 | 41 | def set_hello_hold_time(self, hello_hold_time: int): 42 | self.hello_hold_time = hello_hold_time 43 | if self.neighbor_liveness_timer is not None: 44 | self.neighbor_liveness_timer.cancel() 45 | 46 | if hello_hold_time == HELLO_HOLD_TIME_TIMEOUT: 47 | self.remove() 48 | self.neighbor_logger.debug('Detected neighbor removal of ' + self.ip) 49 | elif hello_hold_time != HELLO_HOLD_TIME_NO_TIMEOUT: 50 | self.neighbor_logger.debug('Neighbor Liveness Timer reseted of ' + self.ip) 51 | self.neighbor_liveness_timer = Timer(hello_hold_time, self.remove) 52 | self.neighbor_liveness_timer.start() 53 | else: 54 | self.neighbor_liveness_timer = None 55 | 56 | def set_generation_id(self, generation_id): 57 | # neighbor restarted 58 | if self.generation_id != generation_id: 59 | self.neighbor_logger.debug('Detected reset of ' + self.ip + '... new GenerationID: ' + str(generation_id)) 60 | self.generation_id = generation_id 61 | self.contact_interface.force_send_hello() 62 | self.reset() 63 | 64 | """ 65 | def heartbeat(self): 66 | if (self.hello_hold_time != HELLO_HOLD_TIME_TIMEOUT) and \ 67 | (self.hello_hold_time != HELLO_HOLD_TIME_NO_TIMEOUT): 68 | print("HEARTBEAT") 69 | if self.neighbor_liveness_timer is not None: 70 | self.neighbor_liveness_timer.cancel() 71 | self.neighbor_liveness_timer = Timer(self.hello_hold_time, self.remove) 72 | self.neighbor_liveness_timer.start() 73 | self.time_of_last_update = time.time() 74 | """ 75 | 76 | def remove(self): 77 | print('HELLO TIMER EXPIRED... remove neighbor') 78 | if self.neighbor_liveness_timer is not None: 79 | self.neighbor_liveness_timer.cancel() 80 | self.neighbor_logger.debug('Neighbor Liveness Timer expired of ' + self.ip) 81 | self.contact_interface.remove_neighbor(self.ip) 82 | 83 | # notify interfaces which have this neighbor as AssertWinner 84 | with self.tree_interface_nlt_subscribers_lock: 85 | for tree_if in self.tree_interface_nlt_subscribers: 86 | tree_if.assert_winner_nlt_expires() 87 | 88 | def reset(self): 89 | self.contact_interface.new_or_reset_neighbor(self.ip) 90 | 91 | def receive_hello(self, generation_id, hello_hold_time, state_refresh_capable): 92 | self.neighbor_logger.debug('Receive Hello message with HelloHoldTime: ' + str(hello_hold_time) + 93 | '; GenerationID: ' + str(generation_id) + '; StateRefreshCapable: ' + 94 | str(state_refresh_capable) + ' from neighbor ' + self.ip) 95 | if hello_hold_time == HELLO_HOLD_TIME_TIMEOUT: 96 | self.set_hello_hold_time(hello_hold_time) 97 | else: 98 | self.time_of_last_update = time.time() 99 | self.set_generation_id(generation_id) 100 | self.set_hello_hold_time(hello_hold_time) 101 | if state_refresh_capable != self.state_refresh_capable: 102 | self.state_refresh_capable = state_refresh_capable 103 | 104 | def subscribe_nlt_expiration(self, tree_if): 105 | with self.tree_interface_nlt_subscribers_lock: 106 | if tree_if not in self.tree_interface_nlt_subscribers: 107 | self.tree_interface_nlt_subscribers.append(tree_if) 108 | 109 | def unsubscribe_nlt_expiration(self, tree_if): 110 | with self.tree_interface_nlt_subscribers_lock: 111 | if tree_if in self.tree_interface_nlt_subscribers: 112 | self.tree_interface_nlt_subscribers.remove(tree_if) 113 | -------------------------------------------------------------------------------- /pimdm/Run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import time 6 | import glob 7 | import socket 8 | import argparse 9 | import threading 10 | import traceback 11 | import _pickle as pickle 12 | from prettytable import PrettyTable 13 | 14 | from pimdm import Main 15 | from pimdm.tree import pim_globals 16 | from pimdm.daemon.Daemon import Daemon 17 | 18 | VERSION = "1.4.0" 19 | 20 | 21 | def client_socket(data_to_send, print_output=True): 22 | # Create a UDS socket 23 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 24 | 25 | # Connect the socket to the port where the server is listening 26 | server_address = pim_globals.DAEMON_SOCKET.format(pim_globals.MULTICAST_TABLE_ID) 27 | #print('connecting to %s' % server_address) 28 | try: 29 | sock.connect(server_address) 30 | sock.sendall(pickle.dumps(data_to_send)) 31 | data_rcv = sock.recv(1024 * 256) 32 | if data_rcv: 33 | if print_output: 34 | print(pickle.loads(data_rcv)) 35 | else: 36 | return pickle.loads(data_rcv) 37 | except socket.error: 38 | pass 39 | finally: 40 | #print('closing socket') 41 | sock.close() 42 | 43 | 44 | class MyDaemon(Daemon): 45 | def run(self): 46 | Main.main() 47 | server_address = pim_globals.DAEMON_SOCKET.format(pim_globals.MULTICAST_TABLE_ID) 48 | 49 | # Make sure the socket does not already exist 50 | try: 51 | os.unlink(server_address) 52 | except OSError: 53 | if os.path.exists(server_address): 54 | raise 55 | 56 | # Create a UDS socket 57 | sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 58 | 59 | # Bind the socket to the port 60 | sock.bind(server_address) 61 | 62 | # Listen for incoming connections 63 | sock.listen(1) 64 | while True: 65 | try: 66 | connection, client_address = sock.accept() 67 | data = connection.recv(256 * 1024) 68 | print(sys.stderr, 'sending data back to the client') 69 | print(pickle.loads(data)) 70 | args = pickle.loads(data) 71 | if 'ipv4' not in args and 'ipv6' not in args or not (args.ipv4 or args.ipv6): 72 | args.ipv4 = True 73 | args.ipv6 = False 74 | 75 | if 'list_interfaces' in args and args.list_interfaces: 76 | connection.sendall(pickle.dumps(Main.list_enabled_interfaces(ipv4=args.ipv4, ipv6=args.ipv6))) 77 | elif 'list_neighbors' in args and args.list_neighbors: 78 | connection.sendall(pickle.dumps(Main.list_neighbors(ipv4=args.ipv4, ipv6=args.ipv6))) 79 | elif 'list_state' in args and args.list_state: 80 | connection.sendall(pickle.dumps(Main.list_state(ipv4=args.ipv4, ipv6=args.ipv6))) 81 | elif 'add_interface' in args and args.add_interface: 82 | Main.add_pim_interface(args.add_interface[0], False, ipv4=args.ipv4, ipv6=args.ipv6) 83 | connection.shutdown(socket.SHUT_RDWR) 84 | elif 'add_interface_sr' in args and args.add_interface_sr: 85 | Main.add_pim_interface(args.add_interface_sr[0], True, ipv4=args.ipv4, ipv6=args.ipv6) 86 | connection.shutdown(socket.SHUT_RDWR) 87 | elif 'add_interface_igmp' in args and args.add_interface_igmp: 88 | Main.add_membership_interface(interface_name=args.add_interface_igmp[0], ipv4=True, ipv6=False) 89 | connection.shutdown(socket.SHUT_RDWR) 90 | elif 'add_interface_mld' in args and args.add_interface_mld: 91 | Main.add_membership_interface(interface_name=args.add_interface_mld[0], ipv4=False, ipv6=True) 92 | connection.shutdown(socket.SHUT_RDWR) 93 | elif 'remove_interface' in args and args.remove_interface: 94 | Main.remove_interface(args.remove_interface[0], pim=True, ipv4=args.ipv4, ipv6=args.ipv6) 95 | connection.shutdown(socket.SHUT_RDWR) 96 | elif 'remove_interface_igmp' in args and args.remove_interface_igmp: 97 | Main.remove_interface(args.remove_interface_igmp[0], membership=True, ipv4=True, ipv6=False) 98 | connection.shutdown(socket.SHUT_RDWR) 99 | elif 'remove_interface_mld' in args and args.remove_interface_mld: 100 | Main.remove_interface(args.remove_interface_mld[0], membership=True, ipv4=False, ipv6=True) 101 | connection.shutdown(socket.SHUT_RDWR) 102 | elif 'list_instances' in args and args.list_instances: 103 | connection.sendall(pickle.dumps(Main.list_instances())) 104 | elif 'stop' in args and args.stop: 105 | Main.stop() 106 | connection.shutdown(socket.SHUT_RDWR) 107 | break 108 | elif 'test' in args and args.test: 109 | Main.test(args.test[0], args.test[1]) 110 | connection.shutdown(socket.SHUT_RDWR) 111 | elif 'config' in args and args.config: 112 | Main.set_config(args.config[0]) 113 | connection.shutdown(socket.SHUT_RDWR) 114 | elif 'get_config' in args and args.get_config: 115 | connection.sendall(pickle.dumps(Main.get_config())) 116 | elif 'drop' in args and args.drop: 117 | Main.drop(args.drop[0], int(args.drop[1])) 118 | except Exception as e: 119 | connection.sendall(pickle.dumps(e)) 120 | connection.shutdown(socket.SHUT_RDWR) 121 | traceback.print_exc() 122 | finally: 123 | # Clean up the connection 124 | connection.close() 125 | sock.close() 126 | 127 | 128 | def main(): 129 | """ 130 | Entry point for PIM-DM 131 | """ 132 | parser = argparse.ArgumentParser(description='PIM-DM protocol', prog='pim-dm') 133 | group = parser.add_mutually_exclusive_group(required=True) 134 | group.add_argument("-start", "--start", action="store_true", default=False, help="Start PIM") 135 | group.add_argument("-stop", "--stop", action="store_true", default=False, help="Stop PIM") 136 | group.add_argument("-restart", "--restart", action="store_true", default=False, help="Restart PIM") 137 | group.add_argument("-li", "--list_interfaces", action="store_true", default=False, help="List All PIM Interfaces. " 138 | "Use -4 or -6 to specify IPv4 or IPv6 interfaces.") 139 | group.add_argument("-ln", "--list_neighbors", action="store_true", default=False, help="List All PIM Neighbors. " 140 | "Use -4 or -6 to specify IPv4 or IPv6 PIM neighbors.") 141 | group.add_argument("-ls", "--list_state", action="store_true", default=False, help="List IGMP/MLD and PIM-DM state machines." 142 | " Use -4 or -6 to specify IPv4 or IPv6 state respectively.") 143 | group.add_argument("-instances", "--list_instances", action="store_true", default=False, 144 | help="List running PIM-DM daemon processes.") 145 | group.add_argument("-mr", "--multicast_routes", action="store_true", default=False, help="List Multicast Routing table. " 146 | "Use -4 or -6 to specify IPv4 or IPv6 multicast routing table.") 147 | group.add_argument("-ai", "--add_interface", nargs=1, metavar='INTERFACE_NAME', help="Add PIM interface. " 148 | "Use -4 or -6 to specify IPv4 or IPv6 interface.") 149 | group.add_argument("-aisr", "--add_interface_sr", nargs=1, metavar='INTERFACE_NAME', help="Add PIM interface with State Refresh enabled. " 150 | "Use -4 or -6 to specify IPv4 or IPv6 interface.") 151 | group.add_argument("-aiigmp", "--add_interface_igmp", nargs=1, metavar='INTERFACE_NAME', help="Add IGMP interface") 152 | group.add_argument("-aimld", "--add_interface_mld", nargs=1, metavar='INTERFACE_NAME', help="Add MLD interface") 153 | group.add_argument("-ri", "--remove_interface", nargs=1, metavar='INTERFACE_NAME', help="Remove PIM interface. " 154 | "Use -4 or -6 to specify IPv4 or IPv6 interface.") 155 | group.add_argument("-riigmp", "--remove_interface_igmp", nargs=1, metavar='INTERFACE_NAME', help="Remove IGMP interface") 156 | group.add_argument("-rimld", "--remove_interface_mld", nargs=1, metavar='INTERFACE_NAME', help="Remove MLD interface") 157 | group.add_argument("-v", "--verbose", action="store_true", default=False, help="Verbose (print all debug messages)") 158 | group.add_argument("-t", "--test", nargs=2, metavar=('ROUTER_NAME', 'SERVER_LOG_IP'), help="Tester... send log information to SERVER_LOG_IP. Set the router name to ROUTER_NAME") 159 | group.add_argument("-config", "--config", nargs=1, metavar='CONFIG_FILE_PATH', type=str, 160 | help="File path for configuration file.") 161 | group.add_argument("-get_config", "--get_config", action="store_true", default=False, 162 | help="Get configuration file of live daemon.") 163 | #group.add_argument("-drop", "--drop", nargs=2, metavar=('INTERFACE_NAME', 'PACKET_TYPE'), type=str) 164 | group.add_argument("--version", action='version', version='%(prog)s ' + VERSION) 165 | group_ipversion = parser.add_mutually_exclusive_group(required=False) 166 | group_ipversion.add_argument("-4", "--ipv4", action="store_true", default=False, help="Setting for IPv4") 167 | group_ipversion.add_argument("-6", "--ipv6", action="store_true", default=False, help="Setting for IPv6") 168 | group_vrf = parser.add_argument_group() 169 | group_vrf.add_argument("-mvrf", "--multicast_vrf", nargs=1, default=[pim_globals.MULTICAST_TABLE_ID], 170 | metavar='MULTICAST_VRF_NUMBER', type=int, 171 | help="Define multicast table id. This can be used on -start to explicitly start the daemon" 172 | " process on a given vrf. It can also be used with the other commands " 173 | "(for example add, list, ...) for setting/getting information on a given daemon" 174 | " process") 175 | group_vrf.add_argument("-uvrf", "--unicast_vrf", nargs=1, default=[pim_globals.UNICAST_TABLE_ID], 176 | metavar='UNICAST_VRF_NUMBER', type=int, 177 | help="Define unicast table id for getting unicast information (RPF checks, RPC costs, ...). " 178 | "This information can only be defined at startup with -start command") 179 | args = parser.parse_args() 180 | 181 | #print(parser.parse_args()) 182 | # This script must be run as root! 183 | if os.geteuid() != 0: 184 | sys.exit('PIM-DM must be run as root!') 185 | 186 | if args.list_instances: 187 | pid_files = glob.glob("/tmp/Daemon-pim*.pid") 188 | t = PrettyTable(['Instance PID', 'Multicast VRF', 'Unicast VRF']) 189 | 190 | for pid_file in pid_files: 191 | d = MyDaemon(pid_file) 192 | pim_globals.MULTICAST_TABLE_ID = pid_file[15:-4] 193 | if not d.is_running(): 194 | continue 195 | 196 | t_new = client_socket(args, print_output=False) 197 | t.add_row(t_new.split("|")) 198 | print(t) 199 | return 200 | 201 | pim_globals.MULTICAST_TABLE_ID = args.multicast_vrf[0] 202 | pim_globals.UNICAST_TABLE_ID = args.unicast_vrf[0] 203 | 204 | daemon = MyDaemon(pim_globals.DAEMON_PROCESS_FILE.format(pim_globals.MULTICAST_TABLE_ID)) 205 | if args.start: 206 | print("start") 207 | daemon.start() 208 | sys.exit(0) 209 | elif args.stop: 210 | client_socket(args) 211 | daemon.stop() 212 | sys.exit(0) 213 | elif args.restart: 214 | daemon.restart() 215 | sys.exit(0) 216 | elif args.config: 217 | try: 218 | from pimdm import Config 219 | args.config[0] = os.path.abspath(args.config[0]) 220 | [pim_globals.MULTICAST_TABLE_ID, pim_globals.UNICAST_TABLE_ID] = Config.get_vrfs(args.config[0]) 221 | daemon = MyDaemon(pim_globals.DAEMON_PROCESS_FILE.format(pim_globals.MULTICAST_TABLE_ID)) 222 | 223 | if not daemon.is_running(): 224 | x = threading.Thread(target=daemon.start, args=()) 225 | x.start() 226 | x.join() 227 | 228 | while not daemon.is_running(): 229 | time.sleep(1) 230 | except ModuleNotFoundError: 231 | print("PYYAML needs to be installed. Execute \"pip3 install pyyaml\"") 232 | sys.exit(0) 233 | except ImportError: 234 | print("PYYAML needs to be installed. Execute \"pip3 install pyyaml\"") 235 | sys.exit(0) 236 | elif args.verbose: 237 | os.system("tail -f {}".format(pim_globals.DAEMON_LOG_STDOUT_FILE.format(pim_globals.MULTICAST_TABLE_ID))) 238 | sys.exit(0) 239 | elif args.multicast_routes: 240 | if args.ipv4 or not args.ipv6: 241 | os.system("ip mroute show table " + str(pim_globals.MULTICAST_TABLE_ID)) 242 | elif args.ipv6: 243 | os.system("ip -6 mroute show table " + str(pim_globals.MULTICAST_TABLE_ID)) 244 | sys.exit(0) 245 | elif not daemon.is_running(): 246 | print("PIM-DM is not running") 247 | parser.print_usage() 248 | sys.exit(0) 249 | 250 | client_socket(args) 251 | 252 | 253 | if __name__ == "__main__": 254 | main() 255 | -------------------------------------------------------------------------------- /pimdm/TestLogger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | class RootFilter(logging.Filter): 4 | """ 5 | This is a filter which injects contextual information into the log. 6 | 7 | Rather than use actual contextual information, we just use random 8 | data in this demo. 9 | """ 10 | def __init__(self, router_name): 11 | super().__init__() 12 | self.router_name = router_name 13 | 14 | def filter(self, record): 15 | record.routername = self.router_name 16 | if not hasattr(record, 'tree'): 17 | record.tree = '' 18 | if not hasattr(record, 'vif'): 19 | record.vif = '' 20 | if not hasattr(record, 'interfacename'): 21 | record.interfacename = '' 22 | if not hasattr(record, 'neighbor_ip'): 23 | record.neighbor_ip = '' 24 | return True 25 | -------------------------------------------------------------------------------- /pimdm/UnicastRouting.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import ipaddress 3 | from threading import RLock 4 | from socket import if_indextoname 5 | from pyroute2 import IPDB 6 | from pimdm.tree import pim_globals 7 | 8 | 9 | def get_route(ip_dst: str): 10 | return UnicastRouting.get_route(ip_dst) 11 | 12 | 13 | def get_metric(ip_dst: str): 14 | return UnicastRouting.get_metric(ip_dst) 15 | 16 | 17 | def check_rpf(ip_dst): 18 | return UnicastRouting.check_rpf(ip_dst) 19 | 20 | 21 | def get_unicast_info(ip_dst): 22 | return UnicastRouting.get_unicast_info(ip_dst) 23 | 24 | 25 | class UnicastRouting(object): 26 | ipdb = None 27 | lock = RLock() 28 | 29 | def __init__(self): 30 | #UnicastRouting.ipr = IPRoute() 31 | UnicastRouting.ipdb = IPDB() 32 | self._ipdb = UnicastRouting.ipdb 33 | self._ipdb.register_callback(UnicastRouting.unicast_changes, mode="post") 34 | 35 | 36 | # get metrics (routing preference and cost) to IP ip_dst 37 | @staticmethod 38 | def get_metric(ip_dst: str): 39 | (metric_administrative_distance, metric_cost, _, _, mask) = UnicastRouting.get_unicast_info(ip_dst) 40 | return (metric_administrative_distance, metric_cost, mask) 41 | 42 | # get output interface IP, used to send data to IP ip_dst 43 | # (root interface IP to ip_dst) 44 | @staticmethod 45 | def check_rpf(ip_dst): 46 | # vif index of rpf interface 47 | return UnicastRouting.get_unicast_info(ip_dst)[3] 48 | 49 | @staticmethod 50 | def get_route(ip_dst: str): 51 | ip_version = ipaddress.ip_address(ip_dst).version 52 | if ip_version == 4: 53 | family = socket.AF_INET 54 | full_mask = 32 55 | elif ip_version == 6: 56 | family = socket.AF_INET6 57 | full_mask = 128 58 | else: 59 | raise Exception("Unknown IP version") 60 | info = None 61 | with UnicastRouting.lock: 62 | ipdb = UnicastRouting.ipdb # type:IPDB 63 | 64 | for mask_len in range(full_mask, 0, -1): 65 | dst_network = str(ipaddress.ip_interface(ip_dst + "/" + str(mask_len)).network) 66 | 67 | print(dst_network) 68 | if dst_network in ipdb.routes.tables[pim_globals.UNICAST_TABLE_ID]: 69 | print(info) 70 | if ipdb.routes[{'dst': dst_network, 'family': family, 71 | 'table': pim_globals.UNICAST_TABLE_ID}]['ipdb_scope'] != 'gc': 72 | info = ipdb.routes[{'dst': dst_network, 'family': family, 'table': pim_globals.UNICAST_TABLE_ID}] 73 | break 74 | else: 75 | continue 76 | if not info: 77 | print("0.0.0.0/0 or ::/0") 78 | if "default" in ipdb.routes.tables[pim_globals.UNICAST_TABLE_ID]: 79 | info = ipdb.routes[{'dst': 'default', 'family': family, 'table': pim_globals.UNICAST_TABLE_ID}] 80 | print(info) 81 | return info 82 | 83 | @staticmethod 84 | def get_unicast_info(ip_dst): 85 | metric_administrative_distance = 0xFFFFFFFF 86 | metric_cost = 0xFFFFFFFF 87 | rpf_node = ip_dst 88 | oif = None 89 | mask = 0 90 | with UnicastRouting.lock: 91 | unicast_route = UnicastRouting.get_route(ip_dst) 92 | if unicast_route is not None: 93 | oif = unicast_route.get("oif") 94 | next_hop = unicast_route["gateway"] 95 | multipaths = unicast_route["multipath"] 96 | #prefsrc = unicast_route.get("prefsrc") 97 | 98 | #rpf_node = ip_dst if (next_hop is None and prefsrc is not None) else next_hop 99 | rpf_node = next_hop if next_hop is not None else ip_dst 100 | if ipaddress.ip_address(ip_dst).version == 4: 101 | highest_ip = ipaddress.ip_address("0.0.0.0") 102 | else: 103 | highest_ip = ipaddress.ip_address("::") 104 | for m in multipaths: 105 | if m.get("gateway", None) is None: 106 | oif = m.get('oif') 107 | rpf_node = ip_dst 108 | break 109 | elif ipaddress.ip_address(m["gateway"]) > highest_ip: 110 | highest_ip = ipaddress.ip_address(m["gateway"]) 111 | oif = m.get('oif') 112 | rpf_node = m["gateway"] 113 | 114 | metric_administrative_distance = unicast_route["proto"] 115 | metric_cost = unicast_route["priority"] 116 | metric_cost = metric_cost if metric_cost is not None else 0 117 | mask = unicast_route["dst_len"] 118 | 119 | interface_name = None if oif is None else if_indextoname(int(oif)) 120 | from pimdm import Main 121 | if ipaddress.ip_address(ip_dst).version == 4: 122 | rpf_if = Main.kernel.vif_name_to_index_dic.get(interface_name) 123 | else: 124 | rpf_if = Main.kernel_v6.vif_name_to_index_dic.get(interface_name) 125 | return (metric_administrative_distance, metric_cost, rpf_node, rpf_if, mask) 126 | 127 | @staticmethod 128 | def unicast_changes(ipdb, msg, action): 129 | """ 130 | Kernel notified about a change 131 | Verify the type of change and recheck all trees if necessary 132 | """ 133 | print("unicast change?") 134 | print(action) 135 | UnicastRouting.lock.acquire() 136 | family = msg['family'] 137 | if action == "RTM_NEWROUTE" or action == "RTM_DELROUTE": 138 | print(ipdb.routes) 139 | mask_len = msg["dst_len"] 140 | network_address = None 141 | attrs = msg["attrs"] 142 | print(attrs) 143 | for (key, value) in attrs: 144 | print((key, value)) 145 | if key == "RTA_DST": 146 | network_address = value 147 | break 148 | if network_address is None and family == socket.AF_INET: 149 | network_address = "0.0.0.0" 150 | elif network_address is None and family == socket.AF_INET6: 151 | network_address = "::" 152 | print(network_address) 153 | print(mask_len) 154 | print(network_address + "/" + str(mask_len)) 155 | subnet = ipaddress.ip_network(network_address + "/" + str(mask_len)) 156 | print(str(subnet)) 157 | UnicastRouting.lock.release() 158 | from pimdm import Main 159 | if family == socket.AF_INET: 160 | Main.kernel.notify_unicast_changes(subnet) 161 | elif family == socket.AF_INET6: 162 | Main.kernel_v6.notify_unicast_changes(subnet) 163 | ''' 164 | elif action == "RTM_NEWADDR" or action == "RTM_DELADDR": 165 | print(action) 166 | print(msg) 167 | interface_name = None 168 | attrs = msg["attrs"] 169 | for (key, value) in attrs: 170 | print((key, value)) 171 | if key == "IFA_LABEL": 172 | interface_name = value 173 | break 174 | UnicastRouting.lock.release() 175 | try: 176 | Main.kernel.notify_interface_changes(interface_name) 177 | except: 178 | import traceback 179 | traceback.print_exc() 180 | pass 181 | subnet = ipaddress.ip_network("0.0.0.0/0") 182 | Main.kernel.notify_unicast_changes(subnet) 183 | elif action == "RTM_NEWLINK" or action == "RTM_DELLINK": 184 | attrs = msg["attrs"] 185 | if_name = None 186 | operation = None 187 | for (key, value) in attrs: 188 | print((key, value)) 189 | if key == "IFLA_IFNAME": 190 | if_name = value 191 | elif key == "IFLA_OPERSTATE": 192 | operation = value 193 | if if_name is not None and operation is not None: 194 | break 195 | if if_name is not None: 196 | print(if_name + ": " + operation) 197 | UnicastRouting.lock.release() 198 | if operation == 'DOWN': 199 | Main.kernel.remove_interface(if_name, membership=True, pim=True) 200 | subnet = ipaddress.ip_network("0.0.0.0/0") 201 | Main.kernel.notify_unicast_changes(subnet) 202 | ''' 203 | else: 204 | UnicastRouting.lock.release() 205 | 206 | def stop(self): 207 | """ 208 | No longer monitor unicast changes.... 209 | Invoked whenever the protocol is stopped 210 | """ 211 | if self._ipdb: 212 | self._ipdb.release() 213 | if UnicastRouting.ipdb: 214 | UnicastRouting.ipdb = None 215 | -------------------------------------------------------------------------------- /pimdm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/__init__.py -------------------------------------------------------------------------------- /pimdm/custom_timer/RemainingTimer.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | 3 | try: 4 | from threading import _Timer as Timer 5 | except ImportError: 6 | from threading import Timer 7 | 8 | class RemainingTimer(Timer): 9 | def __init__(self, interval, function): 10 | super().__init__(interval, function) 11 | self.start_time = time() 12 | 13 | def time_remaining(self): 14 | delta_time = time() - self.start_time 15 | return self.interval - delta_time 16 | 17 | 18 | ''' 19 | def test(): 20 | print("ola") 21 | 22 | x = RemainingTimer(10, test) 23 | x.start() 24 | from time import sleep 25 | for i in range(0, 10): 26 | print(x.time_remaining()) 27 | sleep(1) 28 | ''' 29 | -------------------------------------------------------------------------------- /pimdm/custom_timer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/custom_timer/__init__.py -------------------------------------------------------------------------------- /pimdm/daemon/Daemon.py: -------------------------------------------------------------------------------- 1 | """Generic linux daemon base class for python 3.x.""" 2 | 3 | import sys, os, time, atexit, signal 4 | from pimdm.tree import pim_globals 5 | 6 | 7 | class Daemon: 8 | """A generic Daemon class. 9 | 10 | Usage: subclass the Daemon class and override the run() method.""" 11 | 12 | def __init__(self, pidfile): self.pidfile = pidfile 13 | 14 | def daemonize(self): 15 | """Deamonize class. UNIX double fork mechanism.""" 16 | 17 | try: 18 | pid = os.fork() 19 | if pid > 0: 20 | # exit first parent 21 | sys.exit(0) 22 | except OSError as err: 23 | sys.stderr.write('fork #1 failed: {0}\n'.format(err)) 24 | sys.exit(1) 25 | 26 | # decouple from parent environment 27 | os.makedirs('/var/log/pimdm/', exist_ok=True) 28 | os.chdir('/var/log/pimdm/') 29 | os.setsid() 30 | os.umask(0) 31 | 32 | # do second fork 33 | try: 34 | pid = os.fork() 35 | if pid > 0: 36 | 37 | # exit from second parent 38 | sys.exit(0) 39 | except OSError as err: 40 | sys.stderr.write('fork #2 failed: {0}\n'.format(err)) 41 | sys.exit(1) 42 | 43 | # redirect standard file descriptors 44 | 45 | sys.stdout.flush() 46 | sys.stderr.flush() 47 | si = open(os.devnull, 'r') 48 | so = open('stdout' + str(pim_globals.MULTICAST_TABLE_ID), 'a+') 49 | se = open('stderror' + str(pim_globals.MULTICAST_TABLE_ID), 'a+') 50 | 51 | os.dup2(si.fileno(), sys.stdin.fileno()) 52 | os.dup2(so.fileno(), sys.stdout.fileno()) 53 | os.dup2(se.fileno(), sys.stderr.fileno()) 54 | 55 | # write pidfile 56 | atexit.register(self.delpid) 57 | 58 | pid = str(os.getpid()) 59 | with open(self.pidfile, 'w+') as f: 60 | f.write(pid + '\n') 61 | 62 | def delpid(self): 63 | os.remove(self.pidfile) 64 | 65 | def start(self): 66 | """Start the Daemon.""" 67 | 68 | # Check for a pidfile to see if the Daemon already runs 69 | if self.is_running(): 70 | message = "pidfile {0} already exist. " + \ 71 | "Daemon already running?\n" 72 | sys.stderr.write(message.format(self.pidfile)) 73 | sys.exit(1) 74 | 75 | # Start the Daemon 76 | self.daemonize() 77 | self.run() 78 | 79 | def stop(self): 80 | """Stop the Daemon.""" 81 | 82 | # Get the pid from the pidfile 83 | try: 84 | with open(self.pidfile, 'r') as pf: 85 | pid = int(pf.read().strip()) 86 | except IOError: 87 | pid = None 88 | 89 | if not pid: 90 | message = "pidfile {0} does not exist. " + \ 91 | "Daemon not running?\n" 92 | sys.stderr.write(message.format(self.pidfile)) 93 | return # not an error in a restart 94 | 95 | # Try killing the Daemon process 96 | try: 97 | while 1: 98 | #os.killpg(os.getpgid(pid), signal.SIGTERM) 99 | os.kill(pid, signal.SIGTERM) 100 | time.sleep(0.1) 101 | except OSError as err: 102 | e = str(err.args) 103 | if e.find("No such process") > 0: 104 | if os.path.exists(self.pidfile): 105 | os.remove(self.pidfile) 106 | else: 107 | print(str(err.args)) 108 | sys.exit(1) 109 | 110 | def restart(self): 111 | """Restart the Daemon.""" 112 | self.stop() 113 | self.start() 114 | 115 | def run(self): 116 | """You should override this method when you subclass Daemon. 117 | 118 | It will be called after the process has been daemonized by 119 | start() or restart().""" 120 | 121 | def is_running(self): 122 | try: 123 | with open(self.pidfile, 'r') as pf: 124 | pid = int(pf.read().strip()) 125 | except IOError: 126 | return False 127 | 128 | """ Check For the existence of a unix pid. """ 129 | try: 130 | os.kill(pid, 0) 131 | return True 132 | except: 133 | return False 134 | -------------------------------------------------------------------------------- /pimdm/daemon/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/daemon/__init__.py -------------------------------------------------------------------------------- /pimdm/packet/Packet.py: -------------------------------------------------------------------------------- 1 | from .PacketIpHeader import PacketIpHeader 2 | from .PacketPayload import PacketPayload 3 | 4 | 5 | class Packet(object): 6 | def __init__(self, ip_header: PacketIpHeader = None, payload: PacketPayload = None): 7 | self.ip_header = ip_header 8 | self.payload = payload 9 | 10 | def bytes(self) -> bytes: 11 | return self.payload.bytes() 12 | -------------------------------------------------------------------------------- /pimdm/packet/PacketIpHeader.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | 4 | 5 | class PacketIpHeader: 6 | """ 7 | 0 1 2 3 8 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | |Version| 11 | +-+-+-+-+ 12 | """ 13 | IP_HDR = "! B" 14 | IP_HDR_LEN = struct.calcsize(IP_HDR) 15 | 16 | def __init__(self, ver, hdr_len): 17 | self.version = ver 18 | self.hdr_length = hdr_len 19 | 20 | def __len__(self): 21 | return self.hdr_length 22 | 23 | @staticmethod 24 | def parse_bytes(data: bytes): 25 | (verhlen, ) = struct.unpack(PacketIpHeader.IP_HDR, data[:PacketIpHeader.IP_HDR_LEN]) 26 | ver = (verhlen & 0xF0) >> 4 27 | print("ver:", ver) 28 | return PACKET_HEADER.get(ver).parse_bytes(data) 29 | 30 | 31 | class PacketIpv4Header(PacketIpHeader): 32 | """ 33 | 0 1 2 3 34 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 35 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 36 | |Version| IHL |Type of Service| Total Length | 37 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 38 | | Identification |Flags| Fragment Offset | 39 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 40 | | Time to Live | Protocol | Header Checksum | 41 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 42 | | Source Address | 43 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 44 | | Destination Address | 45 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 46 | | Options | Padding | 47 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 48 | """ 49 | IP_HDR = "! BBH HH BBH 4s 4s" 50 | IP_HDR_LEN = struct.calcsize(IP_HDR) 51 | 52 | def __init__(self, ver, hdr_len, ttl, proto, ip_src, ip_dst): 53 | super().__init__(ver, hdr_len) 54 | self.ttl = ttl 55 | self.proto = proto 56 | self.ip_src = ip_src 57 | self.ip_dst = ip_dst 58 | 59 | def __len__(self): 60 | return self.hdr_length 61 | 62 | @staticmethod 63 | def parse_bytes(data: bytes): 64 | (verhlen, tos, iplen, ipid, frag, ttl, proto, cksum, src, dst) = \ 65 | struct.unpack(PacketIpv4Header.IP_HDR, data[:PacketIpv4Header.IP_HDR_LEN]) 66 | 67 | ver = (verhlen & 0xf0) >> 4 68 | hlen = (verhlen & 0x0f) * 4 69 | 70 | ''' 71 | "VER": ver, 72 | "HLEN": hlen, 73 | "TOS": tos, 74 | "IPLEN": iplen, 75 | "IPID": ipid, 76 | "FRAG": frag, 77 | "TTL": ttl, 78 | "PROTO": proto, 79 | "CKSUM": cksum, 80 | "SRC": socket.inet_ntoa(src), 81 | "DST": socket.inet_ntoa(dst) 82 | ''' 83 | src_ip = socket.inet_ntoa(src) 84 | dst_ip = socket.inet_ntoa(dst) 85 | return PacketIpv4Header(ver, hlen, ttl, proto, src_ip, dst_ip) 86 | 87 | 88 | class PacketIpv6Header(PacketIpHeader): 89 | """ 90 | 0 1 2 3 91 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 92 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 93 | |Version| Traffic Class | Flow Label | 94 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 95 | | Payload Length | Next Header | Hop Limit | 96 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 97 | | | 98 | + + 99 | | | 100 | + Source Address + 101 | | | 102 | + + 103 | | | 104 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 105 | | | 106 | + + 107 | | | 108 | + Destination Address + 109 | | | 110 | + + 111 | | | 112 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 113 | """ 114 | IP6_HDR = "! I HBB 16s 16s" 115 | IP6_HDR_LEN = struct.calcsize(IP6_HDR) 116 | 117 | def __init__(self, ver, next_header, hop_limit, ip_src, ip_dst): 118 | # TODO: confirm hdr_length in case of multiple options/headers 119 | super().__init__(ver, PacketIpv6Header.IP6_HDR_LEN) 120 | self.next_header = next_header 121 | self.hop_limit = hop_limit 122 | self.ip_src = ip_src 123 | self.ip_dst = ip_dst 124 | 125 | def __len__(self): 126 | return PacketIpv6Header.IP6_HDR_LEN 127 | 128 | @staticmethod 129 | def parse_bytes(data: bytes): 130 | (ver_tc_fl, _, next_header, hop_limit, src, dst) = \ 131 | struct.unpack(PacketIpv6Header.IP6_HDR, data[:PacketIpv6Header.IP6_HDR_LEN]) 132 | 133 | ver = (ver_tc_fl & 0xf0000000) >> 28 134 | #tc = (ver_tc_fl & 0x0ff00000) >> 20 135 | #fl = (ver_tc_fl & 0x000fffff) 136 | ''' 137 | "VER": ver, 138 | "TRAFFIC CLASS": tc, 139 | "FLOW LABEL": fl, 140 | "PAYLOAD LEN": payload_length, 141 | "NEXT HEADER": next_header, 142 | "HOP LIMIT": hop_limit, 143 | "SRC": socket.inet_atop(socket.AF_INET6, src), 144 | "DST": socket.inet_atop(socket.AF_INET6, dst) 145 | ''' 146 | 147 | src_ip = socket.inet_ntop(socket.AF_INET6, src) 148 | dst_ip = socket.inet_ntop(socket.AF_INET6, dst) 149 | return PacketIpv6Header(ver, next_header, hop_limit, src_ip, dst_ip) 150 | 151 | 152 | PACKET_HEADER = { 153 | 4: PacketIpv4Header, 154 | 6: PacketIpv6Header, 155 | } 156 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPayload.py: -------------------------------------------------------------------------------- 1 | import abc 2 | 3 | 4 | class PacketPayload(object): 5 | __metaclass__ = abc.ABCMeta 6 | 7 | @abc.abstractmethod 8 | def bytes(self) -> bytes: 9 | """Get packet payload in bytes format""" 10 | 11 | @abc.abstractmethod 12 | def __len__(self): 13 | """Get packet payload length""" 14 | 15 | @staticmethod 16 | @abc.abstractmethod 17 | def parse_bytes(data: bytes): 18 | """From bytes create a object payload""" 19 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimAssert.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | from .PacketPimEncodedGroupAddress import PacketPimEncodedGroupAddress 4 | from .PacketPimEncodedUnicastAddress import PacketPimEncodedUnicastAddress 5 | from pimdm.tree.pim_globals import ASSERT_CANCEL_METRIC 6 | ''' 7 | 0 1 2 3 8 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | |PIM Ver| Type | Reserved | Checksum | 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | | Multicast Group Address (Encoded Group Format) | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | | Source Address (Encoded Unicast Format) | 15 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | |R| Metric Preference | 17 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | | Metric | 19 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | ''' 21 | class PacketPimAssert: 22 | PIM_TYPE = 5 23 | 24 | PIM_HDR_ASSERT = "! %ss %ss LL" 25 | PIM_HDR_ASSERT_WITHOUT_ADDRESS = "! LL" 26 | PIM_HDR_ASSERT_v4 = PIM_HDR_ASSERT % (PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN) 27 | PIM_HDR_ASSERT_v6 = PIM_HDR_ASSERT % (PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN_IPv6, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6) 28 | 29 | PIM_HDR_ASSERT_WITHOUT_ADDRESS_LEN = struct.calcsize(PIM_HDR_ASSERT_WITHOUT_ADDRESS) 30 | PIM_HDR_ASSERT_v4_LEN = struct.calcsize(PIM_HDR_ASSERT_v4) 31 | PIM_HDR_ASSERT_v6_LEN = struct.calcsize(PIM_HDR_ASSERT_v6) 32 | 33 | def __init__(self, multicast_group_address: str or bytes, source_address: str or bytes, metric_preference: int or float, metric: int or float): 34 | if type(multicast_group_address) is bytes: 35 | multicast_group_address = socket.inet_ntoa(multicast_group_address) 36 | if type(source_address) is bytes: 37 | source_address = socket.inet_ntoa(source_address) 38 | if metric_preference > 0x7FFFFFFF: 39 | metric_preference = 0x7FFFFFFF 40 | if metric > ASSERT_CANCEL_METRIC: 41 | metric = ASSERT_CANCEL_METRIC 42 | self.multicast_group_address = multicast_group_address 43 | self.source_address = source_address 44 | self.metric_preference = metric_preference 45 | self.metric = metric 46 | 47 | def bytes(self) -> bytes: 48 | multicast_group_address = PacketPimEncodedGroupAddress(self.multicast_group_address).bytes() 49 | source_address = PacketPimEncodedUnicastAddress(self.source_address).bytes() 50 | 51 | msg = multicast_group_address + source_address + struct.pack(PacketPimAssert.PIM_HDR_ASSERT_WITHOUT_ADDRESS, 52 | 0x7FFFFFFF & self.metric_preference, 53 | self.metric) 54 | return msg 55 | 56 | def __len__(self): 57 | return len(self.bytes()) 58 | 59 | @staticmethod 60 | def parse_bytes(data: bytes): 61 | multicast_group_addr_obj = PacketPimEncodedGroupAddress.parse_bytes(data) 62 | multicast_group_addr_len = len(multicast_group_addr_obj) 63 | data = data[multicast_group_addr_len:] 64 | 65 | source_addr_obj = PacketPimEncodedUnicastAddress.parse_bytes(data) 66 | source_addr_len = len(source_addr_obj) 67 | data = data[source_addr_len:] 68 | 69 | (metric_preference, metric) = struct.unpack(PacketPimAssert.PIM_HDR_ASSERT_WITHOUT_ADDRESS, data[:PacketPimAssert.PIM_HDR_ASSERT_WITHOUT_ADDRESS_LEN]) 70 | pim_payload = PacketPimAssert(multicast_group_addr_obj.group_address, source_addr_obj.unicast_address, 0x7FFFFFFF & metric_preference, metric) 71 | 72 | return pim_payload 73 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimEncodedGroupAddress.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import struct 3 | import socket 4 | ''' 5 | 0 1 2 3 6 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 | | Addr Family | Encoding Type |B| Reserved |Z| Mask Len | 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | | Group Multicast Address 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... 12 | ''' 13 | class PacketPimEncodedGroupAddress: 14 | PIM_ENCODED_GROUP_ADDRESS_HDR = "! BBBB %s" 15 | PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_MULTICAST_ADDRESS = "! BBBB" 16 | 17 | IPV4_HDR = "4s" 18 | IPV6_HDR = "16s" 19 | 20 | # TODO ver melhor versao ip 21 | PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_ADDRESS_LEN = struct.calcsize(PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_MULTICAST_ADDRESS) 22 | PIM_ENCODED_GROUP_ADDRESS_HDR_LEN = struct.calcsize(PIM_ENCODED_GROUP_ADDRESS_HDR % IPV4_HDR) 23 | PIM_ENCODED_GROUP_ADDRESS_HDR_LEN_IPv6 = struct.calcsize(PIM_ENCODED_GROUP_ADDRESS_HDR % IPV6_HDR) 24 | 25 | FAMILY_RESERVED = 0 26 | FAMILY_IPV4 = 1 27 | FAMILY_IPV6 = 2 28 | 29 | RESERVED = 0 30 | 31 | def __init__(self, group_address, mask_len=None): 32 | if type(group_address) not in (str, bytes): 33 | raise Exception 34 | if type(group_address) is bytes: 35 | group_address = socket.inet_ntoa(group_address) 36 | self.group_address = group_address 37 | self.mask_len = mask_len 38 | 39 | def bytes(self) -> bytes: 40 | (string_ip_hdr, hdr_addr_family, socket_family) = PacketPimEncodedGroupAddress.get_ip_info(self.group_address) 41 | mask_len = self.mask_len 42 | if mask_len is None: 43 | mask_len = 8 * struct.calcsize(string_ip_hdr) 44 | ip = socket.inet_pton(socket_family, self.group_address) 45 | 46 | msg = struct.pack(PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR % string_ip_hdr, hdr_addr_family, 0, 47 | PacketPimEncodedGroupAddress.RESERVED, mask_len, ip) 48 | return msg 49 | 50 | @staticmethod 51 | def get_ip_info(ip): 52 | version = ipaddress.ip_address(ip).version 53 | if version == 4: 54 | return (PacketPimEncodedGroupAddress.IPV4_HDR, PacketPimEncodedGroupAddress.FAMILY_IPV4, socket.AF_INET) 55 | elif version == 6: 56 | return (PacketPimEncodedGroupAddress.IPV6_HDR, PacketPimEncodedGroupAddress.FAMILY_IPV6, socket.AF_INET6) 57 | else: 58 | raise Exception("Unknown address family") 59 | 60 | def __len__(self): 61 | version = ipaddress.ip_address(self.group_address).version 62 | if version == 4: 63 | return self.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN 64 | elif version == 6: 65 | return self.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN_IPv6 66 | else: 67 | raise Exception("Unknown address family") 68 | 69 | @staticmethod 70 | def parse_bytes(data: bytes): 71 | data_without_group_addr = data[0:PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_ADDRESS_LEN] 72 | (addr_family, encoding, _, mask_len) = struct.unpack(PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_MULTICAST_ADDRESS, data_without_group_addr) 73 | 74 | data_group_addr = data[PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_WITHOUT_GROUP_ADDRESS_LEN:] 75 | if addr_family == PacketPimEncodedGroupAddress.FAMILY_IPV4: 76 | (ip,) = struct.unpack("! " + PacketPimEncodedGroupAddress.IPV4_HDR, data_group_addr[:4]) 77 | ip = socket.inet_ntop(socket.AF_INET, ip) 78 | elif addr_family == PacketPimEncodedGroupAddress.FAMILY_IPV6: 79 | (ip,) = struct.unpack("! " + PacketPimEncodedGroupAddress.IPV6_HDR, data_group_addr[:16]) 80 | ip = socket.inet_ntop(socket.AF_INET6, ip) 81 | else: 82 | raise Exception("Unknown address family") 83 | 84 | if encoding != 0: 85 | print("unknown encoding") 86 | raise Exception 87 | 88 | return PacketPimEncodedGroupAddress(ip, mask_len) 89 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimEncodedSourceAddress.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import struct 3 | import socket 4 | ''' 5 | 0 1 2 3 6 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 | | Addr Family | Encoding Type | Rsrvd |S|W|R| Mask Len | 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | | Source Address 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... 12 | ''' 13 | class PacketPimEncodedSourceAddress: 14 | PIM_ENCODED_SOURCE_ADDRESS_HDR = "! BBBB %s" 15 | PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS = "! BBBB" 16 | 17 | 18 | IPV4_HDR = "4s" 19 | IPV6_HDR = "16s" 20 | 21 | # TODO ver melhor versao ip 22 | PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS_LEN = struct.calcsize(PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS) 23 | PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN = struct.calcsize(PIM_ENCODED_SOURCE_ADDRESS_HDR % IPV4_HDR) 24 | PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN_IPV6 = struct.calcsize(PIM_ENCODED_SOURCE_ADDRESS_HDR % IPV6_HDR) 25 | 26 | FAMILY_RESERVED = 0 27 | FAMILY_IPV4 = 1 28 | FAMILY_IPV6 = 2 29 | 30 | RESERVED_AND_SWR_BITS = 0 31 | 32 | def __init__(self, source_address, mask_len=None): 33 | if type(source_address) not in (str, bytes): 34 | raise Exception 35 | if type(source_address) is bytes: 36 | source_address = socket.inet_ntoa(source_address) 37 | self.source_address = source_address 38 | self.mask_len = mask_len 39 | 40 | def bytes(self) -> bytes: 41 | (string_ip_hdr, hdr_addr_family, socket_family) = PacketPimEncodedSourceAddress.get_ip_info(self.source_address) 42 | 43 | mask_len = self.mask_len 44 | if mask_len is None: 45 | mask_len = 8 * struct.calcsize(string_ip_hdr) 46 | ip = socket.inet_pton(socket_family, self.source_address) 47 | 48 | msg = struct.pack(PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR % string_ip_hdr, hdr_addr_family, 0, 49 | PacketPimEncodedSourceAddress.RESERVED_AND_SWR_BITS, mask_len, ip) 50 | return msg 51 | 52 | @staticmethod 53 | def get_ip_info(ip): 54 | version = ipaddress.ip_address(ip).version 55 | if version == 4: 56 | return (PacketPimEncodedSourceAddress.IPV4_HDR, PacketPimEncodedSourceAddress.FAMILY_IPV4, socket.AF_INET) 57 | elif version == 6: 58 | return (PacketPimEncodedSourceAddress.IPV6_HDR, PacketPimEncodedSourceAddress.FAMILY_IPV6, socket.AF_INET6) 59 | else: 60 | raise Exception("Unknown address family") 61 | 62 | def __len__(self): 63 | version = ipaddress.ip_address(self.source_address).version 64 | if version == 4: 65 | return self.PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN 66 | elif version == 6: 67 | return self.PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN_IPV6 68 | else: 69 | raise Exception("Unknown address family") 70 | 71 | @staticmethod 72 | def parse_bytes(data: bytes): 73 | data_without_source_addr = data[0:PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS_LEN] 74 | (addr_family, encoding, _, mask_len) = struct.unpack(PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS, data_without_source_addr) 75 | 76 | data_source_addr = data[PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR_WITHOUT_SOURCE_ADDRESS_LEN:] 77 | if addr_family == PacketPimEncodedSourceAddress.FAMILY_IPV4: 78 | (ip,) = struct.unpack("! " + PacketPimEncodedSourceAddress.IPV4_HDR, data_source_addr[:4]) 79 | ip = socket.inet_ntop(socket.AF_INET, ip) 80 | elif addr_family == PacketPimEncodedSourceAddress.FAMILY_IPV6: 81 | (ip,) = struct.unpack("! " + PacketPimEncodedSourceAddress.IPV6_HDR, data_source_addr[:16]) 82 | ip = socket.inet_ntop(socket.AF_INET6, ip) 83 | else: 84 | raise Exception("Unknown address family") 85 | 86 | if encoding != 0: 87 | print("unknown encoding") 88 | raise Exception 89 | 90 | return PacketPimEncodedSourceAddress(ip, mask_len) 91 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimEncodedUnicastAddress.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | import struct 3 | import socket 4 | ''' 5 | 0 1 2 3 6 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 | | Addr Family | Encoding Type | Unicast Address 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+... 10 | ''' 11 | class PacketPimEncodedUnicastAddress: 12 | PIM_ENCODED_UNICAST_ADDRESS_HDR = "! BB %s" 13 | PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS = "! BB" 14 | 15 | IPV4_HDR = "4s" 16 | IPV6_HDR = "16s" 17 | 18 | # TODO ver melhor versao ip 19 | PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS_LEN = struct.calcsize(PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS) 20 | PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN = struct.calcsize(PIM_ENCODED_UNICAST_ADDRESS_HDR % IPV4_HDR) 21 | PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6 = struct.calcsize(PIM_ENCODED_UNICAST_ADDRESS_HDR % IPV6_HDR) 22 | 23 | FAMILY_RESERVED = 0 24 | FAMILY_IPV4 = 1 25 | FAMILY_IPV6 = 2 26 | 27 | def __init__(self, unicast_address): 28 | if type(unicast_address) not in (str, bytes): 29 | raise Exception 30 | if type(unicast_address) is bytes: 31 | unicast_address = socket.inet_ntoa(unicast_address) 32 | self.unicast_address = unicast_address 33 | 34 | def bytes(self) -> bytes: 35 | (string_ip_hdr, hdr_addr_family, socket_family) = PacketPimEncodedUnicastAddress.get_ip_info(self.unicast_address) 36 | 37 | ip = socket.inet_pton(socket_family, self.unicast_address) 38 | msg = struct.pack(PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR % string_ip_hdr, hdr_addr_family, 0, ip) 39 | return msg 40 | 41 | @staticmethod 42 | def get_ip_info(ip): 43 | version = ipaddress.ip_address(ip).version 44 | if version == 4: 45 | return (PacketPimEncodedUnicastAddress.IPV4_HDR, PacketPimEncodedUnicastAddress.FAMILY_IPV4, socket.AF_INET) 46 | elif version == 6: 47 | return (PacketPimEncodedUnicastAddress.IPV6_HDR, PacketPimEncodedUnicastAddress.FAMILY_IPV6, socket.AF_INET6) 48 | else: 49 | raise Exception("Unknown address family") 50 | 51 | def __len__(self): 52 | version = ipaddress.ip_address(self.unicast_address).version 53 | if version == 4: 54 | return self.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN 55 | elif version == 6: 56 | return self.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6 57 | else: 58 | raise Exception("Unknown address family") 59 | 60 | @staticmethod 61 | def parse_bytes(data: bytes): 62 | data_without_unicast_addr = data[0:PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS_LEN] 63 | (addr_family, encoding) = struct.unpack(PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS, data_without_unicast_addr) 64 | 65 | data_unicast_addr = data[PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_WITHOUT_UNICAST_ADDRESS_LEN:] 66 | if addr_family == PacketPimEncodedUnicastAddress.FAMILY_IPV4: 67 | (ip,) = struct.unpack("! " + PacketPimEncodedUnicastAddress.IPV4_HDR, data_unicast_addr[:4]) 68 | ip = socket.inet_ntop(socket.AF_INET, ip) 69 | elif addr_family == PacketPimEncodedUnicastAddress.FAMILY_IPV6: 70 | (ip,) = struct.unpack("! " + PacketPimEncodedUnicastAddress.IPV6_HDR, data_unicast_addr[:16]) 71 | ip = socket.inet_ntop(socket.AF_INET6, ip) 72 | else: 73 | raise Exception("Unknown address family") 74 | 75 | if encoding != 0: 76 | print("unknown encoding") 77 | raise Exception 78 | 79 | return PacketPimEncodedUnicastAddress(ip) 80 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimGraft.py: -------------------------------------------------------------------------------- 1 | from .PacketPimJoinPrune import PacketPimJoinPrune 2 | ''' 3 | 0 1 2 3 4 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 5 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 6 | | Upstream Neighbor Address (Encoded Unicast Format) | 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 | | Reserved | Num Groups | Hold Time | 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | | Multicast Group Address 1 (Encoded Group Format) | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | | Number of Joined Sources | Number of Pruned Sources | 14 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | | Joined Source Address 1 (Encoded Source Format) | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | | . | 18 | | . | 19 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | | Joined Source Address n (Encoded Source Format) | 21 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | | Pruned Source Address 1 (Encoded Source Format) | 23 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | | . | 25 | | . | 26 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | | Pruned Source Address n (Encoded Source Format) | 28 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | | . | 30 | | . | 31 | | . | 32 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | | Multicast Group Address m (Encoded Group Format) | 34 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | | Number of Joined Sources | Number of Pruned Sources | 36 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | | Joined Source Address 1 (Encoded Source Format) | 38 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 39 | | . | 40 | | . | 41 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 42 | ''' 43 | class PacketPimGraft(PacketPimJoinPrune): 44 | PIM_TYPE = 6 45 | 46 | def __init__(self, upstream_neighbor_address, holdtime=0): 47 | super().__init__(upstream_neighbor_address=upstream_neighbor_address, hold_time=holdtime) 48 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimGraftAck.py: -------------------------------------------------------------------------------- 1 | from .PacketPimJoinPrune import PacketPimJoinPrune 2 | ''' 3 | 0 1 2 3 4 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 5 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 6 | | Upstream Neighbor Address (Encoded Unicast Format) | 7 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 8 | | Reserved | Num Groups | Hold Time | 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | | Multicast Group Address 1 (Encoded Group Format) | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | | Number of Joined Sources | Number of Pruned Sources | 14 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | | Joined Source Address 1 (Encoded Source Format) | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | | . | 18 | | . | 19 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | | Joined Source Address n (Encoded Source Format) | 21 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | | Pruned Source Address 1 (Encoded Source Format) | 23 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 24 | | . | 25 | | . | 26 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | | Pruned Source Address n (Encoded Source Format) | 28 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | | . | 30 | | . | 31 | | . | 32 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 33 | | Multicast Group Address m (Encoded Group Format) | 34 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 35 | | Number of Joined Sources | Number of Pruned Sources | 36 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 37 | | Joined Source Address 1 (Encoded Source Format) | 38 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 39 | | . | 40 | | . | 41 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 42 | ''' 43 | class PacketPimGraftAck(PacketPimJoinPrune): 44 | PIM_TYPE = 7 45 | 46 | def __init__(self, upstream_neighbor_address, holdtime=0): 47 | super().__init__(upstream_neighbor_address, hold_time=holdtime) 48 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimHeader.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | from .PacketPimHello import PacketPimHello 4 | from .PacketPimJoinPrune import PacketPimJoinPrune 5 | from .PacketPimAssert import PacketPimAssert 6 | from .PacketPimGraft import PacketPimGraft 7 | from .PacketPimGraftAck import PacketPimGraftAck 8 | from .PacketPimStateRefresh import PacketPimStateRefresh 9 | 10 | 11 | from pimdm.utils import checksum 12 | from .PacketPayload import PacketPayload 13 | ''' 14 | 0 1 2 3 15 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | |PIM Ver| Type | Reserved | Checksum | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | ''' 20 | class PacketPimHeader(PacketPayload): 21 | PIM_VERSION = 2 22 | 23 | PIM_HDR = "! BB H" 24 | PIM_HDR_LEN = struct.calcsize(PIM_HDR) 25 | 26 | PIM_MSG_TYPES = {0: PacketPimHello, 27 | 3: PacketPimJoinPrune, 28 | 5: PacketPimAssert, 29 | 6: PacketPimGraft, 30 | 7: PacketPimGraftAck, 31 | 9: PacketPimStateRefresh 32 | } 33 | 34 | def __init__(self, payload): 35 | self.payload = payload 36 | 37 | def get_pim_type(self): 38 | return self.payload.PIM_TYPE 39 | 40 | def bytes(self) -> bytes: 41 | # obter mensagem e criar checksum 42 | pim_vrs_type = (PacketPimHeader.PIM_VERSION << 4) + self.get_pim_type() 43 | msg_without_chcksum = struct.pack(PacketPimHeader.PIM_HDR, pim_vrs_type, 0, 0) 44 | msg_without_chcksum += self.payload.bytes() 45 | pim_checksum = checksum(msg_without_chcksum) 46 | msg = msg_without_chcksum[0:2] + struct.pack("! H", pim_checksum) + msg_without_chcksum[4:] 47 | return msg 48 | 49 | def __len__(self): 50 | return len(self.bytes()) 51 | 52 | @staticmethod 53 | def parse_bytes(data: bytes): 54 | print("parsePimHdr: ", data) 55 | 56 | pim_hdr = data[0:PacketPimHeader.PIM_HDR_LEN] 57 | (pim_ver_type, reserved, rcv_checksum) = struct.unpack(PacketPimHeader.PIM_HDR, pim_hdr) 58 | 59 | print(pim_ver_type, reserved, rcv_checksum) 60 | pim_version = (pim_ver_type & 0xF0) >> 4 61 | pim_type = pim_ver_type & 0x0F 62 | 63 | if pim_version != PacketPimHeader.PIM_VERSION: 64 | print("Version of PIM packet received not known (!=2)") 65 | raise Exception 66 | 67 | msg_to_checksum = data[0:2] + b'\x00\x00' + data[4:] 68 | if checksum(msg_to_checksum) != rcv_checksum: 69 | print("wrong checksum") 70 | print("checksum calculated: " + str(checksum(msg_to_checksum))) 71 | print("checksum recv: " + str(rcv_checksum)) 72 | raise Exception 73 | 74 | pim_payload = data[PacketPimHeader.PIM_HDR_LEN:] 75 | pim_payload = PacketPimHeader.PIM_MSG_TYPES[pim_type].parse_bytes(pim_payload) 76 | return PacketPimHeader(pim_payload) 77 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimHello.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from abc import ABCMeta, abstractstaticmethod 3 | from .PacketPimHelloOptions import PacketPimHelloOptions, PacketPimHelloStateRefreshCapable, PacketPimHelloGenerationID, PacketPimHelloLANPruneDelay, PacketPimHelloHoldtime 4 | 5 | ''' 6 | 0 1 2 3 7 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 9 | | Option Type | Option Length | 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | | Option Value | 12 | | ... | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | | . | 15 | | . | 16 | | . | 17 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | | Option Type | Option Length | 19 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | | Option Value | 21 | | ... | 22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | ''' 24 | class PacketPimHello: 25 | PIM_TYPE = 0 26 | PIM_HDR_OPTS = "! HH" 27 | PIM_HDR_OPTS_LEN = struct.calcsize(PIM_HDR_OPTS) 28 | 29 | PIM_MSG_TYPES_LENGTH = {1: 2, 30 | 2: 4, 31 | 20: 4, 32 | 21: 4, 33 | } 34 | # todo: pensar melhor na implementacao state refresh capable option... 35 | 36 | def __init__(self): 37 | self.options = {} 38 | 39 | ''' 40 | def add_option(self, option_type: int, option_value: int or float): 41 | option_value = int(option_value) 42 | # if option_value requires more bits than the bits available for that field: option value will have all field bits = 1 43 | if option_type in self.PIM_MSG_TYPES_LENGTH and self.PIM_MSG_TYPES_LENGTH[option_type] * 8 < option_value.bit_length(): 44 | option_value = (1 << (self.PIM_MSG_TYPES_LENGTH[option_type] * 8)) - 1 45 | self.options[option_type] = option_value 46 | ''' 47 | 48 | def add_option(self, option: 'PacketPimHelloOptions'): 49 | #if option_type in self.PIM_MSG_TYPES_LENGTH and self.PIM_MSG_TYPES_LENGTH[option_type] * 8 < option_value.bit_length(): 50 | # option_value = (1 << (self.PIM_MSG_TYPES_LENGTH[option_type] * 8)) - 1 51 | 52 | self.options[option.type] = option 53 | 54 | 55 | def get_options(self): 56 | return self.options 57 | 58 | ''' 59 | def bytes(self) -> bytes: 60 | res = b'' 61 | for (option_type, option_value) in self.options.items(): 62 | option_length = PacketPimHello.PIM_MSG_TYPES_LENGTH[option_type] 63 | type_length_hdr = struct.pack(PacketPimHello.PIM_HDR_OPTS, option_type, option_length) 64 | res += type_length_hdr + struct.pack("! " + str(option_length) + "s", option_value.to_bytes(option_length, byteorder='big')) 65 | return res 66 | ''' 67 | 68 | def bytes(self) -> bytes: 69 | res = b'' 70 | for option in self.options.values(): 71 | res += option.bytes() 72 | return res 73 | 74 | 75 | 76 | def __len__(self): 77 | return len(self.bytes()) 78 | 79 | 80 | ''' 81 | @staticmethod 82 | def parse_bytes(data: bytes): 83 | pim_payload = PacketPimHello() 84 | while data != b'': 85 | (option_type, option_length) = struct.unpack(PacketPimHello.PIM_HDR_OPTS, 86 | data[:PacketPimHello.PIM_HDR_OPTS_LEN]) 87 | print(option_type, option_length) 88 | data = data[PacketPimHello.PIM_HDR_OPTS_LEN:] 89 | print(data) 90 | (option_value,) = struct.unpack("! " + str(option_length) + "s", data[:option_length]) 91 | option_value_number = int.from_bytes(option_value, byteorder='big') 92 | print("option value: ", option_value_number) 93 | 94 | #options_list.append({"OPTION TYPE": option_type, 95 | # "OPTION LENGTH": option_length, 96 | # "OPTION VALUE": option_value_number 97 | # }) 98 | 99 | pim_payload.add_option(option_type, option_value_number) 100 | data = data[option_length:] 101 | 102 | return pim_payload 103 | ''' 104 | 105 | @staticmethod 106 | def parse_bytes(data: bytes): 107 | pim_payload = PacketPimHello() 108 | while data != b'': 109 | option = PacketPimHelloOptions.parse_bytes(data) 110 | option_length = len(option) 111 | data = data[option_length:] 112 | pim_payload.add_option(option) 113 | return pim_payload 114 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimHelloOptions.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from abc import ABCMeta 3 | import math 4 | 5 | class PacketPimHelloOptions(metaclass=ABCMeta): 6 | PIM_HDR_OPTS = "! HH" 7 | PIM_HDR_OPTS_LEN = struct.calcsize(PIM_HDR_OPTS) 8 | ''' 9 | 0 1 2 3 10 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | | Type | Length | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | ''' 15 | def __init__(self, type: int, length: int): 16 | self.type = type 17 | self.length = length 18 | 19 | def bytes(self) -> bytes: 20 | return struct.pack(PacketPimHelloOptions.PIM_HDR_OPTS, self.type, self.length) 21 | 22 | def __len__(self): 23 | return self.PIM_HDR_OPTS_LEN + self.length 24 | 25 | @staticmethod 26 | def parse_bytes(data: bytes, type:int = None, length:int = None): 27 | (type, length) = struct.unpack(PacketPimHelloOptions.PIM_HDR_OPTS, 28 | data[:PacketPimHelloOptions.PIM_HDR_OPTS_LEN]) 29 | #print("TYPE:", type) 30 | #print("LENGTH:", length) 31 | data = data[PacketPimHelloOptions.PIM_HDR_OPTS_LEN:] 32 | #return PIM_MSG_TYPES[type](data) 33 | return PIM_MSG_TYPES.get(type, PacketPimHelloUnknown).parse_bytes(data, type, length) 34 | 35 | 36 | class PacketPimHelloStateRefreshCapable(PacketPimHelloOptions): 37 | PIM_HDR_OPT = "! BBH" 38 | PIM_HDR_OPT_LEN = struct.calcsize(PIM_HDR_OPT) 39 | ''' 40 | 0 1 2 3 41 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 42 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 43 | | Version = 1 | Interval | Reserved | 44 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 45 | ''' 46 | VERSION = 1 47 | 48 | def __init__(self, interval: int): 49 | super().__init__(type=21, length=4) 50 | self.interval = interval 51 | 52 | def bytes(self) -> bytes: 53 | return super().bytes() + struct.pack(self.PIM_HDR_OPT, self.VERSION, self.interval, 0) 54 | 55 | @staticmethod 56 | def parse_bytes(data: bytes, type:int = None, length:int = None): 57 | if type is None or length is None: 58 | raise Exception 59 | (version, interval, _) = struct.unpack(PacketPimHelloStateRefreshCapable.PIM_HDR_OPT, 60 | data[:PacketPimHelloStateRefreshCapable.PIM_HDR_OPT_LEN]) 61 | return PacketPimHelloStateRefreshCapable(interval) 62 | 63 | 64 | 65 | class PacketPimHelloLANPruneDelay(PacketPimHelloOptions): 66 | PIM_HDR_OPT = "! HH" 67 | PIM_HDR_OPT_LEN = struct.calcsize(PIM_HDR_OPT) 68 | ''' 69 | 0 1 2 3 70 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 71 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 72 | |T| LAN Prune Delay | Override Interval | 73 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 74 | ''' 75 | def __init__(self, lan_prune_delay: float, override_interval: float): 76 | super().__init__(type=2, length=4) 77 | self.lan_prune_delay = 0x7FFF & math.ceil(lan_prune_delay) 78 | self.override_interval = math.ceil(override_interval) 79 | 80 | def bytes(self) -> bytes: 81 | return super().bytes() + struct.pack(self.PIM_HDR_OPT, self.lan_prune_delay, self.override_interval) 82 | 83 | @staticmethod 84 | def parse_bytes(data: bytes, type:int = None, length:int = None): 85 | if type is None or length is None: 86 | raise Exception 87 | (lan_prune_delay, override_interval) = struct.unpack(PacketPimHelloLANPruneDelay.PIM_HDR_OPT, 88 | data[:PacketPimHelloLANPruneDelay.PIM_HDR_OPT_LEN]) 89 | lan_prune_delay = lan_prune_delay & 0x7FFF 90 | return PacketPimHelloLANPruneDelay(lan_prune_delay=lan_prune_delay, override_interval=override_interval) 91 | 92 | 93 | 94 | class PacketPimHelloHoldtime(PacketPimHelloOptions): 95 | PIM_HDR_OPT = "! H" 96 | PIM_HDR_OPT_LEN = struct.calcsize(PIM_HDR_OPT) 97 | ''' 98 | 0 1 2 3 99 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 100 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 101 | | Hold Time | 102 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 103 | ''' 104 | def __init__(self, holdtime: int or float): 105 | super().__init__(type=1, length=2) 106 | self.holdtime = int(holdtime) 107 | 108 | def bytes(self) -> bytes: 109 | return super().bytes() + struct.pack(self.PIM_HDR_OPT, self.holdtime) 110 | 111 | @staticmethod 112 | def parse_bytes(data: bytes, type:int = None, length:int = None): 113 | if type is None or length is None: 114 | raise Exception 115 | (holdtime, ) = struct.unpack(PacketPimHelloHoldtime.PIM_HDR_OPT, 116 | data[:PacketPimHelloHoldtime.PIM_HDR_OPT_LEN]) 117 | #print("HOLDTIME:", holdtime) 118 | return PacketPimHelloHoldtime(holdtime=holdtime) 119 | 120 | 121 | 122 | class PacketPimHelloGenerationID(PacketPimHelloOptions): 123 | PIM_HDR_OPT = "! L" 124 | PIM_HDR_OPT_LEN = struct.calcsize(PIM_HDR_OPT) 125 | ''' 126 | 0 1 2 3 127 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 128 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 129 | | Generation ID | 130 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 131 | ''' 132 | def __init__(self, generation_id: int): 133 | super().__init__(type=20, length=4) 134 | self.generation_id = generation_id 135 | 136 | def bytes(self) -> bytes: 137 | return super().bytes() + struct.pack(self.PIM_HDR_OPT, self.generation_id) 138 | 139 | @staticmethod 140 | def parse_bytes(data: bytes, type:int = None, length:int = None): 141 | if type is None or length is None: 142 | raise Exception 143 | (generation_id, ) = struct.unpack(PacketPimHelloGenerationID.PIM_HDR_OPT, 144 | data[:PacketPimHelloGenerationID.PIM_HDR_OPT_LEN]) 145 | #print("GenerationID:", generation_id) 146 | return PacketPimHelloGenerationID(generation_id=generation_id) 147 | 148 | 149 | class PacketPimHelloUnknown(PacketPimHelloOptions): 150 | PIM_HDR_OPT = "! L" 151 | PIM_HDR_OPT_LEN = struct.calcsize(PIM_HDR_OPT) 152 | ''' 153 | 0 1 2 3 154 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 155 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 156 | | Unknown | 157 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 158 | ''' 159 | def __init__(self, type, length): 160 | super().__init__(type=type, length=length) 161 | #print("PIM Hello Option Unknown... TYPE=", type, "LENGTH=", length) 162 | 163 | def bytes(self) -> bytes: 164 | raise Exception 165 | 166 | @staticmethod 167 | def parse_bytes(data: bytes, type:int = None, length:int = None): 168 | if type is None or length is None: 169 | raise Exception 170 | return PacketPimHelloUnknown(type, length) 171 | 172 | 173 | 174 | 175 | 176 | PIM_MSG_TYPES = {1: PacketPimHelloHoldtime, 177 | 2: PacketPimHelloLANPruneDelay, 178 | 20: PacketPimHelloGenerationID, 179 | 21: PacketPimHelloStateRefreshCapable, 180 | } 181 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimJoinPrune.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | from .PacketPimEncodedUnicastAddress import PacketPimEncodedUnicastAddress 4 | from .PacketPimJoinPruneMulticastGroup import PacketPimJoinPruneMulticastGroup 5 | ''' 6 | 0 1 2 3 7 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 9 | | Upstream Neighbor Address (Encoded Unicast Format) | 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | | Reserved | Num Groups | Hold Time | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | ''' 14 | class PacketPimJoinPrune: 15 | PIM_TYPE = 3 16 | 17 | PIM_HDR_JOIN_PRUNE = "! %ss BBH" 18 | PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS = "! BBH" 19 | PIM_HDR_JOIN_PRUNE_v4 = PIM_HDR_JOIN_PRUNE % PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN 20 | PIM_HDR_JOIN_PRUNE_v6 = PIM_HDR_JOIN_PRUNE % PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6 21 | 22 | PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS_LEN = struct.calcsize(PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS) 23 | PIM_HDR_JOIN_PRUNE_v4_LEN = struct.calcsize(PIM_HDR_JOIN_PRUNE_v4) 24 | PIM_HDR_JOIN_PRUNE_v6_LEN = struct.calcsize(PIM_HDR_JOIN_PRUNE_v6) 25 | 26 | def __init__(self, upstream_neighbor_address, hold_time): 27 | if type(upstream_neighbor_address) not in (str, bytes): 28 | raise Exception 29 | if type(upstream_neighbor_address) is bytes: 30 | upstream_neighbor_address = socket.inet_ntoa(upstream_neighbor_address) 31 | self.groups = [] 32 | self.upstream_neighbor_address = upstream_neighbor_address 33 | self.hold_time = hold_time 34 | 35 | def add_multicast_group(self, group: PacketPimJoinPruneMulticastGroup): 36 | # TODO verificar se grupo ja esta na msg 37 | self.groups.append(group) 38 | 39 | def bytes(self) -> bytes: 40 | upstream_neighbor_address = PacketPimEncodedUnicastAddress(self.upstream_neighbor_address).bytes() 41 | msg = upstream_neighbor_address + struct.pack(PacketPimJoinPrune.PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS, 0, 42 | len(self.groups), self.hold_time) 43 | 44 | for multicast_group in self.groups: 45 | msg += multicast_group.bytes() 46 | 47 | return msg 48 | 49 | def __len__(self): 50 | return len(self.bytes()) 51 | 52 | @classmethod 53 | def parse_bytes(cls, data: bytes): 54 | upstream_neighbor_addr_obj = PacketPimEncodedUnicastAddress.parse_bytes(data) 55 | upstream_neighbor_addr_len = len(upstream_neighbor_addr_obj) 56 | data = data[upstream_neighbor_addr_len:] 57 | 58 | (_, num_groups, hold_time) = struct.unpack(PacketPimJoinPrune.PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS, 59 | data[:PacketPimJoinPrune.PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS_LEN]) 60 | data = data[PacketPimJoinPrune.PIM_HDR_JOIN_PRUNE_WITHOUT_ADDRESS_LEN:] 61 | pim_payload = cls(upstream_neighbor_addr_obj.unicast_address, hold_time) 62 | 63 | for i in range(0, num_groups): 64 | group = PacketPimJoinPruneMulticastGroup.parse_bytes(data) 65 | group_len = len(group) 66 | 67 | pim_payload.add_multicast_group(group) 68 | data = data[group_len:] 69 | 70 | return pim_payload 71 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimJoinPruneMulticastGroup.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | from .PacketPimEncodedGroupAddress import PacketPimEncodedGroupAddress 4 | from .PacketPimEncodedSourceAddress import PacketPimEncodedSourceAddress 5 | 6 | ''' 7 | 0 1 2 3 8 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 10 | | Multicast Group Address 1 (Encoded Group Format) | 11 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 12 | | Number of Joined Sources | Number of Pruned Sources | 13 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | | Joined Source Address 1 (Encoded Source Format) | 15 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | | . | 17 | | . | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | | Joined Source Address n (Encoded Source Format) | 20 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | | Pruned Source Address 1 (Encoded Source Format) | 22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | | . | 24 | | . | 25 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 26 | | Pruned Source Address n (Encoded Source Format) | 27 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | ''' 29 | 30 | 31 | class PacketPimJoinPruneMulticastGroup: 32 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP = "! %ss HH" 33 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS = "! HH" 34 | 35 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_v4_LEN_ = struct.calcsize( 36 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP % PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN) 37 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_v6_LEN_ = struct.calcsize( 38 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP % PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN_IPv6) 39 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS_LEN = struct.calcsize( 40 | PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS) 41 | 42 | PIM_HDR_JOINED_PRUNED_SOURCE = "! %ss" 43 | PIM_HDR_JOINED_PRUNED_SOURCE_v4_LEN = PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN 44 | PIM_HDR_JOINED_PRUNED_SOURCE_v6_LEN = PacketPimEncodedSourceAddress.PIM_ENCODED_SOURCE_ADDRESS_HDR_LEN_IPV6 45 | 46 | def __init__(self, multicast_group: str or bytes, joined_src_addresses: list=[], pruned_src_addresses: list=[]): 47 | if type(multicast_group) not in (str, bytes): 48 | raise Exception 49 | elif type(multicast_group) is bytes: 50 | multicast_group = socket.inet_ntoa(multicast_group) 51 | 52 | if type(joined_src_addresses) is not list: 53 | raise Exception 54 | if type(pruned_src_addresses) is not list: 55 | raise Exception 56 | 57 | self.multicast_group = multicast_group 58 | self.joined_src_addresses = joined_src_addresses 59 | self.pruned_src_addresses = pruned_src_addresses 60 | 61 | def bytes(self) -> bytes: 62 | multicast_group_address = PacketPimEncodedGroupAddress(self.multicast_group).bytes() 63 | msg = multicast_group_address + struct.pack(self.PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS, 64 | len(self.joined_src_addresses), len(self.pruned_src_addresses)) 65 | 66 | for joined_src_address in self.joined_src_addresses: 67 | joined_src_address_bytes = PacketPimEncodedSourceAddress(joined_src_address).bytes() 68 | msg += joined_src_address_bytes 69 | 70 | for pruned_src_address in self.pruned_src_addresses: 71 | pruned_src_address_bytes = PacketPimEncodedSourceAddress(pruned_src_address).bytes() 72 | msg += pruned_src_address_bytes 73 | return msg 74 | 75 | def __len__(self): 76 | return len(self.bytes()) 77 | 78 | @staticmethod 79 | def parse_bytes(data: bytes): 80 | multicast_group_addr_obj = PacketPimEncodedGroupAddress.parse_bytes(data) 81 | multicast_group_addr_len = len(multicast_group_addr_obj) 82 | data = data[multicast_group_addr_len:] 83 | 84 | number_join_prune_data = data[:PacketPimJoinPruneMulticastGroup.PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS_LEN] 85 | (number_joined_sources, number_pruned_sources) = struct.unpack(PacketPimJoinPruneMulticastGroup.PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS, number_join_prune_data) 86 | 87 | joined = [] 88 | pruned = [] 89 | data = data[PacketPimJoinPruneMulticastGroup.PIM_HDR_JOIN_PRUNE_MULTICAST_GROUP_WITHOUT_GROUP_ADDRESS_LEN:] 90 | for i in range(0, number_joined_sources): 91 | joined_obj = PacketPimEncodedSourceAddress.parse_bytes(data) 92 | joined_obj_len = len(joined_obj) 93 | data = data[joined_obj_len:] 94 | joined.append(joined_obj.source_address) 95 | 96 | for i in range(0, number_pruned_sources): 97 | pruned_obj = PacketPimEncodedSourceAddress.parse_bytes(data) 98 | pruned_obj_len = len(pruned_obj) 99 | data = data[pruned_obj_len:] 100 | pruned.append(pruned_obj.source_address) 101 | 102 | return PacketPimJoinPruneMulticastGroup(multicast_group_addr_obj.group_address, joined, pruned) 103 | -------------------------------------------------------------------------------- /pimdm/packet/PacketPimStateRefresh.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | from .PacketPimEncodedUnicastAddress import PacketPimEncodedUnicastAddress 4 | from .PacketPimEncodedGroupAddress import PacketPimEncodedGroupAddress 5 | ''' 6 | 0 1 2 3 7 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 8 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 9 | |PIM Ver| Type | Reserved | Checksum | 10 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 11 | | Multicast Group Address (Encoded Group Format) | 12 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 13 | | Source Address (Encoded Unicast Format) | 14 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | | Originator Address (Encoded Unicast Format) | 16 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | |R| Metric Preference | 18 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | | Metric | 20 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | | Masklen | TTL |P|N|O|Reserved | Interval | 22 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | ''' 24 | class PacketPimStateRefresh: 25 | PIM_TYPE = 9 26 | 27 | PIM_HDR_STATE_REFRESH = "! %ss %ss %ss I I BBBB" 28 | PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES = "! I I BBBB" 29 | PIM_HDR_STATE_REFRESH_v4 = PIM_HDR_STATE_REFRESH % (PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN) 30 | PIM_HDR_STATE_REFRESH_v6 = PIM_HDR_STATE_REFRESH % (PacketPimEncodedGroupAddress.PIM_ENCODED_GROUP_ADDRESS_HDR_LEN_IPv6, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6, PacketPimEncodedUnicastAddress.PIM_ENCODED_UNICAST_ADDRESS_HDR_LEN_IPV6) 31 | 32 | PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES_LEN = struct.calcsize(PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES) 33 | PIM_HDR_STATE_REFRESH_v4_LEN = struct.calcsize(PIM_HDR_STATE_REFRESH_v4) 34 | PIM_HDR_STATE_REFRESH_v6_LEN = struct.calcsize(PIM_HDR_STATE_REFRESH_v6) 35 | 36 | def __init__(self, multicast_group_adress: str or bytes, source_address: str or bytes, originator_adress: str or bytes, 37 | metric_preference: int, metric: int, mask_len: int, ttl: int, prune_indicator_flag: bool, 38 | prune_now_flag: bool, assert_override_flag: bool, interval: int): 39 | 40 | if type(multicast_group_adress) is bytes: 41 | multicast_group_adress = socket.inet_ntoa(multicast_group_adress) 42 | if type(source_address) is bytes: 43 | source_address = socket.inet_ntoa(source_address) 44 | if type(originator_adress) is bytes: 45 | originator_adress = socket.inet_ntoa(originator_adress) 46 | 47 | self.multicast_group_adress = multicast_group_adress 48 | self.source_address = source_address 49 | self.originator_adress = originator_adress 50 | self.metric_preference = metric_preference 51 | self.metric = metric 52 | self.mask_len = mask_len 53 | self.ttl = ttl 54 | self.prune_indicator_flag = prune_indicator_flag 55 | self.prune_now_flag = prune_now_flag 56 | self.assert_override_flag = assert_override_flag 57 | self.interval = interval 58 | 59 | def bytes(self) -> bytes: 60 | multicast_group_adress = PacketPimEncodedGroupAddress(self.multicast_group_adress).bytes() 61 | source_address = PacketPimEncodedUnicastAddress(self.source_address).bytes() 62 | originator_adress = PacketPimEncodedUnicastAddress(self.originator_adress).bytes() 63 | prune_and_assert_flags = (self.prune_indicator_flag << 7) | (self.prune_now_flag << 6) | (self.assert_override_flag << 5) 64 | 65 | msg = multicast_group_adress + source_address + originator_adress + \ 66 | struct.pack(self.PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES, 0x7FFFFFFF & self.metric_preference, 67 | self.metric, self.mask_len, self.ttl, prune_and_assert_flags, self.interval) 68 | 69 | return msg 70 | 71 | def __len__(self): 72 | return len(self.bytes()) 73 | 74 | @staticmethod 75 | def parse_bytes(data: bytes): 76 | multicast_group_adress_obj = PacketPimEncodedGroupAddress.parse_bytes(data) 77 | multicast_group_adress_len = len(multicast_group_adress_obj) 78 | data = data[multicast_group_adress_len:] 79 | 80 | source_address_obj = PacketPimEncodedUnicastAddress.parse_bytes(data) 81 | source_address_len = len(source_address_obj) 82 | data = data[source_address_len:] 83 | 84 | originator_address_obj = PacketPimEncodedUnicastAddress.parse_bytes(data) 85 | originator_address_len = len(originator_address_obj) 86 | data = data[originator_address_len:] 87 | 88 | (metric_preference, metric, mask_len, ttl, reserved_and_prune_and_assert_flags, interval) = struct.unpack(PacketPimStateRefresh.PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES, data[:PacketPimStateRefresh.PIM_HDR_STATE_REFRESH_WITHOUT_ADDRESSES_LEN]) 89 | metric_preference = 0x7FFFFFFF & metric_preference 90 | prune_indicator_flag = (0x80 & reserved_and_prune_and_assert_flags) >> 7 91 | prune_now_flag = (0x40 & reserved_and_prune_and_assert_flags) >> 6 92 | assert_override_flag = (0x20 & reserved_and_prune_and_assert_flags) >> 5 93 | 94 | pim_payload = PacketPimStateRefresh(multicast_group_adress_obj.group_address, source_address_obj.unicast_address, 95 | originator_address_obj.unicast_address, metric_preference, metric, mask_len, 96 | ttl, prune_indicator_flag, prune_now_flag, assert_override_flag, interval) 97 | 98 | return pim_payload 99 | -------------------------------------------------------------------------------- /pimdm/packet/ReceivedPacket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from .Packet import Packet 3 | from .PacketPimHeader import PacketPimHeader 4 | from .PacketIpHeader import PacketIpv4Header, PacketIpv6Header 5 | from pimdm.utils import TYPE_CHECKING 6 | if TYPE_CHECKING: 7 | from pimdm.Interface import Interface 8 | 9 | 10 | class ReceivedPacket(Packet): 11 | # choose payload protocol class based on ip protocol number 12 | payload_protocol = {103: PacketPimHeader} 13 | 14 | def __init__(self, raw_packet: bytes, interface: 'Interface'): 15 | self.interface = interface 16 | 17 | # Parse packet and fill Packet super class 18 | ip_header = PacketIpv4Header.parse_bytes(raw_packet) 19 | protocol_number = ip_header.proto 20 | 21 | packet_without_ip_hdr = raw_packet[ip_header.hdr_length:] 22 | payload = ReceivedPacket.payload_protocol[protocol_number].parse_bytes(packet_without_ip_hdr) 23 | 24 | super().__init__(ip_header=ip_header, payload=payload) 25 | 26 | 27 | class ReceivedPacket_v6(Packet): 28 | # choose payload protocol class based on ip protocol number 29 | payload_protocol_v6 = {103: PacketPimHeader} 30 | 31 | def __init__(self, raw_packet: bytes, ancdata: list, src_addr: str, next_header: int, interface: 'Interface'): 32 | self.interface = interface 33 | 34 | # Parse packet and fill Packet super class 35 | dst_addr = "::" 36 | for cmsg_level, cmsg_type, cmsg_data in ancdata: 37 | if cmsg_level == socket.IPPROTO_IPV6 and cmsg_type == socket.IPV6_PKTINFO: 38 | dst_addr = socket.inet_ntop(socket.AF_INET6, cmsg_data[:16]) 39 | break 40 | 41 | src_addr = src_addr[0].split("%")[0] 42 | ipv6_packet = PacketIpv6Header(ver=6, hop_limit=1, next_header=next_header, ip_src=src_addr, ip_dst=dst_addr) 43 | payload = ReceivedPacket_v6.payload_protocol_v6[next_header].parse_bytes(raw_packet) 44 | super().__init__(ip_header=ipv6_packet, payload=payload) 45 | -------------------------------------------------------------------------------- /pimdm/packet/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/packet/__init__.py -------------------------------------------------------------------------------- /pimdm/rwlock/RWLock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Read Write Lock 5 | """ 6 | 7 | import threading 8 | import time 9 | 10 | class RWLockRead(object): 11 | """ 12 | A Read/Write lock giving preference to Reader 13 | """ 14 | def __init__(self): 15 | self.V_ReadCount = 0 16 | self.A_Resource = threading.Lock() 17 | self.A_LockReadCount = threading.Lock() 18 | class _aReader(object): 19 | def __init__(self, p_RWLock): 20 | self.A_RWLock = p_RWLock 21 | self.V_Locked = False 22 | 23 | def acquire(self, blocking=1, timeout=-1): 24 | p_TimeOut = None if (blocking and timeout < 0) else (timeout if blocking else 0) 25 | c_DeadLine = None if p_TimeOut is None else (time.time() + p_TimeOut) 26 | if not self.A_RWLock.A_LockReadCount.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 27 | return False 28 | self.A_RWLock.V_ReadCount += 1 29 | if self.A_RWLock.V_ReadCount == 1: 30 | if not self.A_RWLock.A_Resource.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 31 | self.A_RWLock.V_ReadCount -= 1 32 | self.A_RWLock.A_LockReadCount.release() 33 | return False 34 | self.A_RWLock.A_LockReadCount.release() 35 | self.V_Locked = True 36 | return True 37 | 38 | def release(self): 39 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 40 | self.V_Locked = False 41 | self.A_RWLock.A_LockReadCount.acquire() 42 | self.A_RWLock.V_ReadCount -= 1 43 | if self.A_RWLock.V_ReadCount == 0: 44 | self.A_RWLock.A_Resource.release() 45 | self.A_RWLock.A_LockReadCount.release() 46 | def locked(self): 47 | return self.V_Locked 48 | def __enter__(self): 49 | self.acquire() 50 | def __exit__(self, p_Type, p_Value, p_Traceback): 51 | self.release() 52 | class _aWriter(object): 53 | def __init__(self, p_RWLock): 54 | self.A_RWLock = p_RWLock 55 | self.V_Locked = False 56 | def acquire(self, blocking=1, timeout=-1): 57 | self.V_Locked = self.A_RWLock.A_Resource.acquire(blocking, timeout) 58 | return self.V_Locked 59 | def release(self): 60 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 61 | self.V_Locked = False 62 | self.A_RWLock.A_Resource.release() 63 | def locked(self): 64 | return self.V_Locked 65 | def __enter__(self): 66 | self.acquire() 67 | def __exit__(self, p_Type, p_Value, p_Traceback): 68 | self.release() 69 | def genRlock(self): 70 | """ 71 | Generate a reader lock 72 | """ 73 | return RWLockRead._aReader(self) 74 | def genWlock(self): 75 | """ 76 | Generate a writer lock 77 | """ 78 | return RWLockRead._aWriter(self) 79 | 80 | class RWLockWrite(object): 81 | """ 82 | A Read/Write lock giving preference to Writer 83 | """ 84 | def __init__(self): 85 | self.V_ReadCount = 0 86 | self.V_WriteCount = 0 87 | self.A_LockReadCount = threading.Lock() 88 | self.A_LockWriteCount = threading.Lock() 89 | self.A_LockReadEntry = threading.Lock() 90 | self.A_LockReadTry = threading.Lock() 91 | self.A_Resource = threading.Lock() 92 | class _aReader(object): 93 | def __init__(self, p_RWLock): 94 | self.A_RWLock = p_RWLock 95 | self.V_Locked = False 96 | def acquire(self, blocking=1, timeout=-1): 97 | p_TimeOut = None if (blocking and timeout < 0) else (timeout if blocking else 0) 98 | c_DeadLine = None if p_TimeOut is None else (time.time() + p_TimeOut) 99 | if not self.A_RWLock.A_LockReadEntry.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 100 | return False 101 | if not self.A_RWLock.A_LockReadTry.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 102 | self.A_RWLock.A_LockReadEntry.release() 103 | return False 104 | if not self.A_RWLock.A_LockReadCount.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 105 | self.A_RWLock.A_LockReadTry.release() 106 | self.A_RWLock.A_LockReadEntry.release() 107 | return False 108 | self.A_RWLock.V_ReadCount += 1 109 | if (self.A_RWLock.V_ReadCount == 1): 110 | if not self.A_RWLock.A_Resource.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 111 | self.A_RWLock.A_LockReadTry.release() 112 | self.A_RWLock.A_LockReadEntry.release() 113 | self.A_RWLock.V_ReadCount -= 1 114 | self.A_RWLock.A_LockReadCount.release() 115 | return False 116 | self.A_RWLock.A_LockReadCount.release() 117 | self.A_RWLock.A_LockReadTry.release() 118 | self.A_RWLock.A_LockReadEntry.release() 119 | self.V_Locked = True 120 | return True 121 | def release(self): 122 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 123 | self.V_Locked = False 124 | self.A_RWLock.A_LockReadCount.acquire() 125 | self.A_RWLock.V_ReadCount -= 1 126 | if (self.A_RWLock.V_ReadCount == 0): 127 | self.A_RWLock.A_Resource.release() 128 | self.A_RWLock.A_LockReadCount.release() 129 | def locked(self): 130 | return self.V_Locked 131 | def __enter__(self): 132 | self.acquire() 133 | def __exit__(self, p_Type, p_Value, p_Traceback): 134 | self.release() 135 | class _aWriter(object): 136 | def __init__(self, p_RWLock): 137 | self.A_RWLock = p_RWLock 138 | self.V_Locked = False 139 | def acquire(self, blocking=1, timeout=-1): 140 | p_TimeOut = None if (blocking and timeout < 0) else (timeout if blocking else 0) 141 | c_DeadLine = None if p_TimeOut is None else (time.time() + p_TimeOut) 142 | if not self.A_RWLock.A_LockWriteCount.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 143 | return False 144 | self.A_RWLock.V_WriteCount += 1 145 | if (self.A_RWLock.V_WriteCount == 1): 146 | if not self.A_RWLock.A_LockReadTry.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 147 | self.A_RWLock.V_WriteCount -= 1 148 | self.A_RWLock.A_LockWriteCount.release() 149 | return False 150 | self.A_RWLock.A_LockWriteCount.release() 151 | if not self.A_RWLock.A_Resource.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 152 | self.A_RWLock.A_LockWriteCount.acquire() 153 | self.A_RWLock.V_WriteCount -= 1 154 | if self.A_RWLock.V_WriteCount == 0: 155 | self.A_RWLock.A_LockReadTry.release() 156 | self.A_RWLock.A_LockWriteCount.release() 157 | return False 158 | self.V_Locked = True 159 | return True 160 | def release(self): 161 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 162 | self.V_Locked = False 163 | self.A_RWLock.A_Resource.release() 164 | self.A_RWLock.A_LockWriteCount.acquire() 165 | self.A_RWLock.V_WriteCount -= 1 166 | if (self.A_RWLock.V_WriteCount == 0): 167 | self.A_RWLock.A_LockReadTry.release() 168 | self.A_RWLock.A_LockWriteCount.release() 169 | def locked(self): 170 | return self.V_Locked 171 | def __enter__(self): 172 | self.acquire() 173 | def __exit__(self, p_Type, p_Value, p_Traceback): 174 | self.release() 175 | def genRlock(self): 176 | """ 177 | Generate a reader lock 178 | """ 179 | return RWLockWrite._aReader(self) 180 | def genWlock(self): 181 | """ 182 | Generate a writer lock 183 | """ 184 | return RWLockWrite._aWriter(self) 185 | 186 | class RWLockFair(object): 187 | """ 188 | A Read/Write lock giving fairness to both Reader and Writer 189 | """ 190 | def __init__(self): 191 | self.V_ReadCount = 0 192 | self.A_LockReadCount = threading.Lock() 193 | self.A_LockRead = threading.Lock() 194 | self.A_LockWrite = threading.Lock() 195 | class _aReader(object): 196 | def __init__(self, p_RWLock): 197 | self.A_RWLock = p_RWLock 198 | self.V_Locked = False 199 | def acquire(self, blocking=1, timeout=-1): 200 | p_TimeOut = None if (blocking and timeout < 0) else (timeout if blocking else 0) 201 | c_DeadLine = None if p_TimeOut is None else (time.time() + p_TimeOut) 202 | if not self.A_RWLock.A_LockRead.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 203 | return False 204 | if not self.A_RWLock.A_LockReadCount.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 205 | self.A_RWLock.A_LockRead.release() 206 | return False 207 | self.A_RWLock.V_ReadCount += 1 208 | if self.A_RWLock.V_ReadCount == 1: 209 | if not self.A_RWLock.A_LockWrite.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 210 | self.A_RWLock.V_ReadCount -= 1 211 | self.A_RWLock.A_LockReadCount.release() 212 | self.A_RWLock.A_LockRead.release() 213 | return False 214 | self.A_RWLock.A_LockReadCount.release() 215 | self.A_RWLock.A_LockRead.release() 216 | self.V_Locked = True 217 | return True 218 | def release(self): 219 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 220 | self.V_Locked = False 221 | self.A_RWLock.A_LockReadCount.acquire() 222 | self.A_RWLock.V_ReadCount -= 1 223 | if self.A_RWLock.V_ReadCount == 0: 224 | self.A_RWLock.A_LockWrite.release() 225 | self.A_RWLock.A_LockReadCount.release() 226 | def locked(self): 227 | return self.V_Locked 228 | def __enter__(self): 229 | self.acquire() 230 | def __exit__(self, p_Type, p_Value, p_Traceback): 231 | self.release() 232 | class _aWriter(object): 233 | def __init__(self, p_RWLock): 234 | self.A_RWLock = p_RWLock 235 | self.V_Locked = False 236 | def acquire(self, blocking=1, timeout=-1): 237 | p_TimeOut = None if (blocking and timeout < 0) else (timeout if blocking else 0) 238 | c_DeadLine = None if p_TimeOut is None else (time.time() + p_TimeOut) 239 | if not self.A_RWLock.A_LockRead.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 240 | return False 241 | if not self.A_RWLock.A_LockWrite.acquire(blocking=1, timeout=-1 if c_DeadLine is None else max(0, c_DeadLine - time.time())): 242 | self.A_RWLock.A_LockRead.release() 243 | return False 244 | self.V_Locked = True 245 | return True 246 | def release(self): 247 | if not self.V_Locked: raise RuntimeError("cannot release un-acquired lock") 248 | self.V_Locked = False 249 | self.A_RWLock.A_LockWrite.release() 250 | self.A_RWLock.A_LockRead.release() 251 | def locked(self): 252 | return self.V_Locked 253 | def __enter__(self): 254 | self.acquire() 255 | def __exit__(self, p_Type, p_Value, p_Traceback): 256 | self.release() 257 | def genRlock(self): 258 | """ 259 | Generate a reader lock 260 | """ 261 | return RWLockFair._aReader(self) 262 | def genWlock(self): 263 | """ 264 | Generate a writer lock 265 | """ 266 | return RWLockFair._aWriter(self) 267 | -------------------------------------------------------------------------------- /pimdm/rwlock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/rwlock/__init__.py -------------------------------------------------------------------------------- /pimdm/tree/KernelEntry.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from time import time 3 | from threading import Lock, RLock 4 | 5 | from pimdm import UnicastRouting 6 | from .metric import AssertMetric 7 | from .tree_if_upstream import TreeInterfaceUpstream 8 | from .tree_if_downstream import TreeInterfaceDownstream 9 | from .tree_interface import TreeInterface 10 | 11 | 12 | class KernelEntry: 13 | KERNEL_LOGGER = logging.getLogger('pim.KernelEntry') 14 | 15 | def __init__(self, source_ip: str, group_ip: str, kernel_entry_interface): 16 | self.kernel_entry_logger = logging.LoggerAdapter(KernelEntry.KERNEL_LOGGER, 17 | {'tree': '(' + source_ip + ',' + group_ip + ')'}) 18 | self.kernel_entry_logger.debug('Create KernelEntry') 19 | 20 | self.source_ip = source_ip 21 | self.group_ip = group_ip 22 | 23 | self._kernel_entry_interface = kernel_entry_interface 24 | 25 | # OBTAIN UNICAST ROUTING INFORMATION################################################### 26 | (metric_administrative_distance, metric_cost, rpf_node, root_if, mask) = \ 27 | UnicastRouting.get_unicast_info(source_ip) 28 | if root_if is None: 29 | raise Exception 30 | self.rpf_node = rpf_node 31 | 32 | # (S,G) starts IG state 33 | self._was_olist_null = False 34 | 35 | # Locks 36 | self._multicast_change = Lock() 37 | self._lock_test2 = RLock() 38 | self.CHANGE_STATE_LOCK = RLock() 39 | 40 | # decide inbound interface based on rpf check 41 | self.inbound_interface_index = root_if 42 | 43 | self.interface_state = {} # type: Dict[int, TreeInterface] 44 | with self.CHANGE_STATE_LOCK: 45 | for i in self.get_kernel().vif_index_to_name_dic.keys(): 46 | try: 47 | if i == self.inbound_interface_index: 48 | self.interface_state[i] = TreeInterfaceUpstream(self, i) 49 | else: 50 | self.interface_state[i] = TreeInterfaceDownstream(self, i) 51 | except: 52 | import traceback 53 | print(traceback.print_exc()) 54 | continue 55 | 56 | self.change() 57 | self.evaluate_olist_change() 58 | self.timestamp_of_last_state_refresh_message_received = 0 59 | print('Tree created') 60 | 61 | def get_inbound_interface_index(self): 62 | """ 63 | Get VIF of root interface of this tree 64 | """ 65 | return self.inbound_interface_index 66 | 67 | def get_outbound_interfaces_indexes(self): 68 | """ 69 | Get OIL of this tree 70 | """ 71 | return self._kernel_entry_interface.get_outbound_interfaces_indexes(self) 72 | 73 | ################################################ 74 | # Receive (S,G) data packets or control packets 75 | ################################################ 76 | def recv_data_msg(self, index): 77 | """ 78 | Receive data packet regarding this tree in interface with VIF index 79 | """ 80 | print("recv data") 81 | self.interface_state[index].recv_data_msg() 82 | 83 | def recv_assert_msg(self, index, packet): 84 | """ 85 | Receive assert packet regarding this tree in interface with VIF index 86 | """ 87 | print("recv assert") 88 | pkt_assert = packet.payload.payload 89 | metric = pkt_assert.metric 90 | metric_preference = pkt_assert.metric_preference 91 | assert_sender_ip = packet.ip_header.ip_src 92 | 93 | received_metric = AssertMetric(metric_preference=metric_preference, route_metric=metric, ip_address=assert_sender_ip) 94 | self.interface_state[index].recv_assert_msg(received_metric) 95 | 96 | def recv_prune_msg(self, index, packet): 97 | """ 98 | Receive Prune packet regarding this tree in interface with VIF index 99 | """ 100 | print("recv prune msg") 101 | holdtime = packet.payload.payload.hold_time 102 | upstream_neighbor_address = packet.payload.payload.upstream_neighbor_address 103 | self.interface_state[index].recv_prune_msg(upstream_neighbor_address=upstream_neighbor_address, holdtime=holdtime) 104 | 105 | def recv_join_msg(self, index, packet): 106 | """ 107 | Receive Join packet regarding this tree in interface with VIF index 108 | """ 109 | print("recv join msg") 110 | upstream_neighbor_address = packet.payload.payload.upstream_neighbor_address 111 | self.interface_state[index].recv_join_msg(upstream_neighbor_address) 112 | 113 | def recv_graft_msg(self, index, packet): 114 | """ 115 | Receive Graft packet regarding this tree in interface with VIF index 116 | """ 117 | print("recv graft msg") 118 | upstream_neighbor_address = packet.payload.payload.upstream_neighbor_address 119 | source_ip = packet.ip_header.ip_src 120 | self.interface_state[index].recv_graft_msg(upstream_neighbor_address, source_ip) 121 | 122 | def recv_graft_ack_msg(self, index, packet): 123 | """ 124 | Receive GraftAck packet regarding this tree in interface with VIF index 125 | """ 126 | print("recv graft ack msg") 127 | source_ip = packet.ip_header.ip_src 128 | self.interface_state[index].recv_graft_ack_msg(source_ip) 129 | 130 | def recv_state_refresh_msg(self, index, packet): 131 | """ 132 | Receive StateRefresh packet regarding this tree in interface with VIF index 133 | """ 134 | print("recv state refresh msg") 135 | source_of_state_refresh = packet.ip_header.ip_src 136 | 137 | metric_preference = packet.payload.payload.metric_preference 138 | metric = packet.payload.payload.metric 139 | ttl = packet.payload.payload.ttl 140 | prune_indicator_flag = packet.payload.payload.prune_indicator_flag #P 141 | interval = packet.payload.payload.interval 142 | received_metric = AssertMetric(metric_preference=metric_preference, route_metric=metric, ip_address=source_of_state_refresh, state_refresh_interval=interval) 143 | self.interface_state[index].recv_state_refresh_msg(received_metric, prune_indicator_flag) 144 | 145 | iif = packet.interface.vif_index 146 | if iif != self.inbound_interface_index: 147 | return 148 | if self.interface_state[iif].get_neighbor_RPF() != source_of_state_refresh: 149 | return 150 | # refresh limit 151 | timestamp = time() 152 | if (timestamp - self.timestamp_of_last_state_refresh_message_received) < interval - 5: 153 | return 154 | self.timestamp_of_last_state_refresh_message_received = timestamp 155 | if ttl == 0: 156 | return 157 | 158 | self.forward_state_refresh_msg(packet.payload.payload) 159 | 160 | ################################################ 161 | # Send state refresh msg 162 | ################################################ 163 | def forward_state_refresh_msg(self, state_refresh_packet): 164 | """ 165 | Forward StateRefresh packet through all interfaces 166 | """ 167 | for interface in self.interface_state.values(): 168 | interface.send_state_refresh(state_refresh_packet) 169 | 170 | 171 | ############################################################### 172 | # Unicast Changes to RPF 173 | ############################################################### 174 | def network_update(self): 175 | """ 176 | Unicast routing table suffered an update and this tree might be affected by it 177 | """ 178 | # TODO TALVEZ OUTRO LOCK PARA BLOQUEAR ENTRADA DE PACOTES 179 | with self.CHANGE_STATE_LOCK: 180 | 181 | (metric_administrative_distance, metric_cost, rpf_node, new_inbound_interface_index, _) = \ 182 | UnicastRouting.get_unicast_info(self.source_ip) 183 | 184 | if new_inbound_interface_index is None: 185 | self.delete() 186 | return 187 | if new_inbound_interface_index != self.inbound_interface_index: 188 | self.rpf_node = rpf_node 189 | 190 | # get old interfaces 191 | old_upstream_interface = self.interface_state.get(self.inbound_interface_index, None) 192 | old_downstream_interface = self.interface_state.get(new_inbound_interface_index, None) 193 | 194 | # change type of interfaces 195 | if self.inbound_interface_index is not None: 196 | new_downstream_interface = TreeInterfaceDownstream(self, self.inbound_interface_index) 197 | self.interface_state[self.inbound_interface_index] = new_downstream_interface 198 | new_upstream_interface = None 199 | if new_inbound_interface_index is not None: 200 | new_upstream_interface = TreeInterfaceUpstream(self, new_inbound_interface_index) 201 | self.interface_state[new_inbound_interface_index] = new_upstream_interface 202 | self.inbound_interface_index = new_inbound_interface_index 203 | 204 | # remove old interfaces 205 | if old_upstream_interface is not None: 206 | old_upstream_interface.delete(change_type_interface=True) 207 | if old_downstream_interface is not None: 208 | old_downstream_interface.delete(change_type_interface=True) 209 | 210 | # atualizar tabela de encaminhamento multicast 211 | #self._was_olist_null = False 212 | self.change() 213 | self.evaluate_olist_change() 214 | if new_upstream_interface is not None: 215 | new_upstream_interface.change_on_unicast_routing(interface_change=True) 216 | elif self.rpf_node != rpf_node: 217 | self.rpf_node = rpf_node 218 | self.interface_state[self.inbound_interface_index].change_on_unicast_routing() 219 | 220 | def change_at_number_of_neighbors(self): 221 | """ 222 | Check if modification of number of neighbors causes changes to OIL and interest of interface 223 | """ 224 | with self.CHANGE_STATE_LOCK: 225 | self.change() 226 | self.evaluate_olist_change() 227 | 228 | def new_or_reset_neighbor(self, if_index, neighbor_ip): 229 | """ 230 | An interface identified by if_index has a new neighbor 231 | """ 232 | # todo maybe lock de interfaces 233 | self.interface_state[if_index].new_or_reset_neighbor(neighbor_ip) 234 | 235 | def is_olist_null(self): 236 | """ 237 | Check if olist is null 238 | """ 239 | for interface in self.interface_state.values(): 240 | if interface.is_forwarding(): 241 | return False 242 | return True 243 | 244 | def evaluate_olist_change(self): 245 | """ 246 | React to changes on the olist 247 | """ 248 | with self._lock_test2: 249 | is_olist_null = self.is_olist_null() 250 | 251 | if self._was_olist_null != is_olist_null: 252 | if is_olist_null: 253 | self.interface_state[self.inbound_interface_index].olist_is_null() 254 | else: 255 | self.interface_state[self.inbound_interface_index].olist_is_not_null() 256 | 257 | self._was_olist_null = is_olist_null 258 | 259 | def get_source(self): 260 | """ 261 | Get source IP of multicast source 262 | """ 263 | return self.source_ip 264 | 265 | def get_group(self): 266 | """ 267 | Get group IP of multicast tree 268 | """ 269 | return self.group_ip 270 | 271 | def change(self): 272 | """ 273 | Trigger an update on the multicast routing table 274 | """ 275 | with self._multicast_change: 276 | self.get_kernel().set_multicast_route(self) 277 | 278 | def delete(self): 279 | """ 280 | Remove kernel entry 281 | """ 282 | with self._multicast_change: 283 | for state in self.interface_state.values(): 284 | state.delete() 285 | 286 | self.get_kernel().remove_multicast_route(self) 287 | 288 | def get_interface_name(self, interface_id): 289 | """ 290 | Get interface name of interface identified by interface_id 291 | """ 292 | return self._kernel_entry_interface.get_interface_name(interface_id) 293 | 294 | def get_interface(self, interface_id): 295 | """ 296 | Get PIM interface 297 | """ 298 | return self._kernel_entry_interface.get_interface(self, interface_id) 299 | 300 | def get_membership_interface(self, interface_id): 301 | """ 302 | Get IGMP/MLD interface 303 | """ 304 | return self._kernel_entry_interface.get_membership_interface(self, interface_id) 305 | 306 | def get_kernel(self): 307 | """ 308 | Get kernel 309 | """ 310 | return self._kernel_entry_interface.get_kernel() 311 | 312 | ###################################### 313 | # Interface change 314 | ####################################### 315 | def new_interface(self, index): 316 | """ 317 | React to a new interface that was added and in which a tree was already built 318 | """ 319 | with self.CHANGE_STATE_LOCK: 320 | self.interface_state[index] = TreeInterfaceDownstream(self, index) 321 | self.change() 322 | self.evaluate_olist_change() 323 | 324 | def remove_interface(self, index): 325 | """ 326 | React to removal of an interface of a tree that was already built 327 | """ 328 | with self.CHANGE_STATE_LOCK: 329 | #check if removed interface is root interface 330 | if self.inbound_interface_index == index: 331 | self.delete() 332 | elif index in self.interface_state: 333 | self.interface_state.pop(index).delete() 334 | self.change() 335 | self.evaluate_olist_change() 336 | -------------------------------------------------------------------------------- /pimdm/tree/KernelEntryInterface.py: -------------------------------------------------------------------------------- 1 | from pimdm import Main 2 | from abc import abstractmethod, ABCMeta 3 | 4 | 5 | class KernelEntryInterface(metaclass=ABCMeta): 6 | @staticmethod 7 | @abstractmethod 8 | def get_outbound_interfaces_indexes(kernel_tree): 9 | """ 10 | Get OIL of this tree 11 | """ 12 | pass 13 | 14 | @staticmethod 15 | @abstractmethod 16 | def get_interface_name(interface_id): 17 | """ 18 | Get name of interface from vif id 19 | """ 20 | pass 21 | 22 | @staticmethod 23 | @abstractmethod 24 | def get_interface(kernel_tree, interface_id): 25 | """ 26 | Get PIM interface from interface id 27 | """ 28 | pass 29 | 30 | @staticmethod 31 | @abstractmethod 32 | def get_membership_interface(kernel_tree, interface_id): 33 | """ 34 | Get IGMP/MLD interface from interface id 35 | """ 36 | pass 37 | 38 | @staticmethod 39 | @abstractmethod 40 | def get_kernel(): 41 | """ 42 | Get kernel 43 | """ 44 | pass 45 | 46 | 47 | class KernelEntry4Interface(KernelEntryInterface): 48 | @staticmethod 49 | def get_outbound_interfaces_indexes(kernel_tree): 50 | """ 51 | Get OIL of this tree 52 | """ 53 | outbound_indexes = [0] * Main.kernel.MAXVIFS 54 | for (index, state) in kernel_tree.interface_state.items(): 55 | outbound_indexes[index] = state.is_forwarding() 56 | return outbound_indexes 57 | 58 | @staticmethod 59 | def get_interface_name(interface_id): 60 | """ 61 | Get name of interface from vif id 62 | """ 63 | return Main.kernel.vif_index_to_name_dic[interface_id] 64 | 65 | @staticmethod 66 | def get_interface(kernel_tree, interface_id): 67 | """ 68 | Get PIM interface from interface id 69 | """ 70 | interface_name = kernel_tree.get_interface_name(interface_id) 71 | return Main.interfaces.get(interface_name, None) 72 | 73 | @staticmethod 74 | def get_membership_interface(kernel_tree, interface_id): 75 | """ 76 | Get IGMP interface from interface id 77 | """ 78 | interface_name = kernel_tree.get_interface_name(interface_id) 79 | return Main.igmp_interfaces.get(interface_name, None) # type: InterfaceIGMP 80 | 81 | @staticmethod 82 | def get_kernel(): 83 | """ 84 | Get kernel 85 | """ 86 | return Main.kernel 87 | 88 | 89 | class KernelEntry6Interface(KernelEntryInterface): 90 | @staticmethod 91 | def get_outbound_interfaces_indexes(kernel_tree): 92 | """ 93 | Get OIL of this tree 94 | """ 95 | outbound_indexes = [0] * 8 96 | for (index, state) in kernel_tree.interface_state.items(): 97 | outbound_indexes[index // 32] |= state.is_forwarding() << (index % 32) 98 | return outbound_indexes 99 | 100 | @staticmethod 101 | def get_interface_name(interface_id): 102 | """ 103 | Get name of interface from vif id 104 | """ 105 | return Main.kernel_v6.vif_index_to_name_dic[interface_id] 106 | 107 | @staticmethod 108 | def get_interface(kernel_tree, interface_id): 109 | """ 110 | Get PIM interface from interface id 111 | """ 112 | interface_name = kernel_tree.get_interface_name(interface_id) 113 | return Main.interfaces_v6.get(interface_name, None) 114 | 115 | @staticmethod 116 | def get_membership_interface(kernel_tree, interface_id): 117 | """ 118 | Get MLD interface from interface id 119 | """ 120 | interface_name = kernel_tree.get_interface_name(interface_id) 121 | return Main.mld_interfaces.get(interface_name, None) # type: InterfaceMLD 122 | 123 | @staticmethod 124 | def get_kernel(): 125 | """ 126 | Get kernel 127 | """ 128 | return Main.kernel_v6 129 | -------------------------------------------------------------------------------- /pimdm/tree/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pedrofran12/pim_dm/521ea5aa055ed4d9a84a7b208e894c671431521f/pimdm/tree/__init__.py -------------------------------------------------------------------------------- /pimdm/tree/data_packets_socket.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import socket 3 | import ipaddress 4 | from ctypes import create_string_buffer, addressof 5 | from pcap_wrapper import bpf 6 | 7 | SO_ATTACH_FILTER = 26 8 | ETH_P_IP = 0x0800 # Internet Protocol packet 9 | ETH_P_IPV6 = 0x86DD # IPv6 over bluebook 10 | 11 | SO_RCVBUFFORCE = 33 12 | 13 | def get_s_g_bpf_filter_code(source, group, interface_name): 14 | ip_source_version = ipaddress.ip_address(source).version 15 | ip_group_version = ipaddress.ip_address(group).version 16 | if ip_source_version == ip_group_version == 4: 17 | # bpf_filter_str = "(udp or icmp) and host %s and dst %s" % (source, group) 18 | bpf_filter_str = "(ip proto not 2) and host %s and dst %s" % (source, group) 19 | protocol = ETH_P_IP 20 | elif ip_source_version == ip_group_version == 6: 21 | # TODO: allow ICMPv6 echo request/echo response to be considered multicast packets 22 | bpf_filter_str = "(ip6 proto not 58) and host %s and dst %s" % (source, group) 23 | protocol = ETH_P_IPV6 24 | else: 25 | raise Exception("Unknown IP family") 26 | 27 | num, bpf_filter = bpf(bpf_filter_str.encode()).compiled_filter() 28 | print(num) 29 | 30 | # defined in linux/filter.h. 31 | b = create_string_buffer(bpf_filter) 32 | mem_addr_of_filters = addressof(b) 33 | fprog = struct.pack('HL', num, mem_addr_of_filters) 34 | 35 | 36 | # Create listening socket with filters 37 | s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, protocol) 38 | s.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, fprog) 39 | # todo pequeno ajuste (tamanho de buffer pequeno para o caso de trafego em rajadas): 40 | #s.setsockopt(socket.SOL_SOCKET, SO_RCVBUFFORCE, 1) 41 | s.bind((interface_name, protocol)) 42 | 43 | return s 44 | -------------------------------------------------------------------------------- /pimdm/tree/downstream_prune.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | from . import pim_globals as pim_globals 4 | from pimdm.utils import TYPE_CHECKING 5 | if TYPE_CHECKING: 6 | from .tree_if_downstream import TreeInterfaceDownstream 7 | 8 | class DownstreamStateABS(metaclass=ABCMeta): 9 | @staticmethod 10 | @abstractmethod 11 | def receivedPrune(interface: "TreeInterfaceDownstream", holdtime): 12 | """ 13 | Receive Prune(S,G) 14 | 15 | @type interface: Downstream 16 | """ 17 | raise NotImplementedError() 18 | 19 | @staticmethod 20 | @abstractmethod 21 | def receivedJoin(interface: "TreeInterfaceDownstream"): 22 | """ 23 | Receive Join(S,G) 24 | 25 | @type interface: Downstream 26 | """ 27 | raise NotImplementedError() 28 | 29 | @staticmethod 30 | @abstractmethod 31 | def receivedGraft(interface: "TreeInterfaceDownstream", source_ip): 32 | """ 33 | Receive Graft(S,G) 34 | 35 | @type interface: Downstream 36 | """ 37 | raise NotImplementedError() 38 | 39 | @staticmethod 40 | @abstractmethod 41 | def PPTexpires(interface: "TreeInterfaceDownstream"): 42 | """ 43 | PPT(S,G) Expires 44 | 45 | @type interface: Downstream 46 | """ 47 | raise NotImplementedError() 48 | 49 | @staticmethod 50 | @abstractmethod 51 | def PTexpires(interface: "TreeInterfaceDownstream"): 52 | """ 53 | PT(S,G) Expires 54 | 55 | @type interface: Downstream 56 | """ 57 | raise NotImplementedError() 58 | 59 | @staticmethod 60 | @abstractmethod 61 | def is_now_RPF_Interface(interface: "TreeInterfaceDownstream"): 62 | """ 63 | RPF_Interface(S) becomes I 64 | 65 | @type interface: Downstream 66 | """ 67 | raise NotImplementedError() 68 | 69 | @staticmethod 70 | @abstractmethod 71 | def send_state_refresh(interface: "TreeInterfaceDownstream"): 72 | """ 73 | Send State Refresh(S,G) out I 74 | 75 | @type interface: Downstream 76 | """ 77 | raise NotImplementedError() 78 | 79 | def __str__(self): 80 | return "Downstream." + self.__class__.__name__ 81 | 82 | 83 | class NoInfo(DownstreamStateABS): 84 | ''' 85 | NoInfo(NI) 86 | The interface has no (S,G) Prune state, and neither the Prune 87 | timer (PT(S,G,I)) nor the PrunePending timer ((PPT(S,G,I)) is 88 | running. 89 | ''' 90 | 91 | @staticmethod 92 | def receivedPrune(interface: "TreeInterfaceDownstream", holdtime): 93 | """ 94 | Receive Prune(S,G) 95 | 96 | @type interface: TreeInterfaceDownstreamDownstream 97 | """ 98 | interface.join_prune_logger.debug("receivedPrune, NI -> PP") 99 | interface.set_prune_state(DownstreamState.PrunePending) 100 | 101 | time = 0 102 | if len(interface.get_interface().neighbors) > 1: 103 | time = pim_globals.JP_OVERRIDE_INTERVAL 104 | 105 | interface.set_prune_pending_timer(time) 106 | 107 | @staticmethod 108 | def receivedJoin(interface: "TreeInterfaceDownstream"): 109 | """ 110 | Receive Join(S,G) 111 | 112 | @type interface: TreeInterfaceDownstreamDownstream 113 | """ 114 | # Do nothing 115 | interface.join_prune_logger.debug("receivedJoin, NI -> NI") 116 | 117 | @staticmethod 118 | def receivedGraft(interface: "TreeInterfaceDownstream", source_ip): 119 | """ 120 | Receive Graft(S,G) 121 | 122 | @type interface: TreeInterfaceDownstreamDownstream 123 | """ 124 | interface.join_prune_logger.debug('receivedGraft, NI -> NI') 125 | interface.send_graft_ack(source_ip) 126 | 127 | @staticmethod 128 | def PPTexpires(interface: "TreeInterfaceDownstream"): 129 | """ 130 | PPT(S,G) Expires 131 | 132 | @type interface: TreeInterfaceDownstreamDownstream 133 | """ 134 | #assert False, "PPTexpires in state NI" 135 | return 136 | 137 | @staticmethod 138 | def PTexpires(interface: "TreeInterfaceDownstream"): 139 | """ 140 | PT(S,G) Expires 141 | 142 | @type interface: TreeInterfaceDownstreamDownstream 143 | """ 144 | #assert False, "PTexpires in state NI" 145 | return 146 | 147 | @staticmethod 148 | def is_now_RPF_Interface(interface: "TreeInterfaceDownstream"): 149 | """ 150 | RPF_Interface(S) becomes I 151 | 152 | @type interface: TreeInterfaceDownstreamDownstream 153 | """ 154 | # Do nothing 155 | return 156 | 157 | @staticmethod 158 | def send_state_refresh(interface: "TreeInterfaceDownstream"): 159 | """ 160 | Send State Refresh(S,G) out I 161 | 162 | @type interface: TreeInterfaceDownstreamDownstream 163 | """ 164 | # Do nothing 165 | return 166 | 167 | def __str__(self): 168 | return "NoInfo" 169 | 170 | 171 | 172 | class PrunePending(DownstreamStateABS): 173 | ''' 174 | PrunePending(PP) 175 | The router has received a Prune(S,G) on this interface from a 176 | downstream neighbor and is waiting to see whether the prune will 177 | be overridden by another downstream router. For forwarding 178 | purposes, the PrunePending state functions exactly like the 179 | NoInfo state. 180 | ''' 181 | 182 | @staticmethod 183 | def receivedPrune(interface: "TreeInterfaceDownstream", holdtime): 184 | """ 185 | Receive Prune(S,G) 186 | 187 | @type interface: TreeInterfaceDownstreamDownstream 188 | """ 189 | interface.join_prune_logger.debug('receivedPrune, PP -> PP') 190 | 191 | 192 | @staticmethod 193 | def receivedJoin(interface: "TreeInterfaceDownstream"): 194 | """ 195 | Receive Join(S,G) 196 | 197 | @type interface: TreeInterfaceDownstreamDownstream 198 | """ 199 | interface.join_prune_logger.debug('receivedJoin, PP -> NI') 200 | 201 | interface.clear_prune_pending_timer() 202 | 203 | interface.set_prune_state(DownstreamState.NoInfo) 204 | 205 | 206 | @staticmethod 207 | def receivedGraft(interface: "TreeInterfaceDownstream", source_ip): 208 | """ 209 | Receive Graft(S,G) 210 | 211 | @type interface: TreeInterfaceDownstreamDownstream 212 | """ 213 | interface.join_prune_logger.debug('receivedGraft, PP -> NI') 214 | 215 | interface.clear_prune_pending_timer() 216 | 217 | interface.set_prune_state(DownstreamState.NoInfo) 218 | interface.send_graft_ack(source_ip) 219 | 220 | @staticmethod 221 | def PPTexpires(interface: "TreeInterfaceDownstream"): 222 | """ 223 | PPT(S,G) Expires 224 | 225 | @type interface: TreeInterfaceDownstreamDownstream 226 | """ 227 | interface.join_prune_logger.debug('PPTexpires, PP -> P') 228 | interface.set_prune_state(DownstreamState.Pruned) 229 | interface.set_prune_timer(interface.get_received_prune_holdtime() - pim_globals.JP_OVERRIDE_INTERVAL) 230 | 231 | if len(interface.get_interface().neighbors) > 1: 232 | interface.send_pruneecho() 233 | 234 | @staticmethod 235 | def PTexpires(interface: "TreeInterfaceDownstream"): 236 | """ 237 | PT(S,G) Expires 238 | 239 | @type interface: TreeInterfaceDownstreamDownstream 240 | """ 241 | #assert False, "PTexpires in state PP" 242 | return 243 | 244 | @staticmethod 245 | def is_now_RPF_Interface(interface: "TreeInterfaceDownstream"): 246 | """ 247 | RPF_Interface(S) becomes I 248 | 249 | @type interface: TreeInterfaceDownstreamDownstream 250 | """ 251 | interface.join_prune_logger.debug('is_now_RPF_Interface, PP -> NI') 252 | 253 | interface.clear_prune_pending_timer() 254 | 255 | interface.set_prune_state(DownstreamState.NoInfo) 256 | 257 | @staticmethod 258 | def send_state_refresh(interface: "TreeInterfaceDownstream"): 259 | """ 260 | Send State Refresh(S,G) out I 261 | 262 | @type interface: TreeInterfaceDownstreamDownstream 263 | """ 264 | return 265 | 266 | def __str__(self): 267 | return "PrunePending" 268 | 269 | class Pruned(DownstreamStateABS): 270 | ''' 271 | Pruned(P) 272 | The router has received a Prune(S,G) on this interface from a 273 | downstream neighbor, and the Prune was not overridden. Data from 274 | S addressed to group G is no longer being forwarded on this 275 | interface. 276 | ''' 277 | 278 | @staticmethod 279 | def receivedPrune(interface: "TreeInterfaceDownstream", holdtime): 280 | """ 281 | Receive Prune(S,G) 282 | 283 | @type interface: TreeInterfaceDownstreamDownstream 284 | """ 285 | interface.join_prune_logger.debug('receivedPrune, P -> P') 286 | if holdtime > interface.remaining_prune_timer(): 287 | interface.set_prune_timer(holdtime) 288 | 289 | @staticmethod 290 | def receivedJoin(interface: "TreeInterfaceDownstream"): 291 | """ 292 | Receive Join(S,G) 293 | 294 | @type interface: TreeInterfaceDownstreamDownstream 295 | """ 296 | interface.join_prune_logger.debug('receivedPrune, P -> NI') 297 | 298 | interface.clear_prune_timer() 299 | 300 | interface.set_prune_state(DownstreamState.NoInfo) 301 | 302 | @staticmethod 303 | def receivedGraft(interface: "TreeInterfaceDownstream", source_ip): 304 | """ 305 | Receive Graft(S,G) 306 | 307 | @type interface: TreeInterfaceDownstreamDownstream 308 | """ 309 | interface.join_prune_logger.debug('receivedGraft, P -> NI') 310 | interface.clear_prune_timer() 311 | interface.set_prune_state(DownstreamState.NoInfo) 312 | interface.send_graft_ack(source_ip) 313 | 314 | @staticmethod 315 | def PPTexpires(interface: "TreeInterfaceDownstream"): 316 | """ 317 | PPT(S,G) Expires 318 | 319 | @type interface: TreeInterfaceDownstreamDownstream 320 | """ 321 | #assert False, "PPTexpires in state P" 322 | return 323 | 324 | @staticmethod 325 | def PTexpires(interface: "TreeInterfaceDownstream"): 326 | """ 327 | PT(S,G) Expires 328 | 329 | @type interface: TreeInterfaceDownstreamDownstream 330 | """ 331 | interface.join_prune_logger.debug('PTexpires, P -> NI') 332 | interface.set_prune_state(DownstreamState.NoInfo) 333 | 334 | @staticmethod 335 | def is_now_RPF_Interface(interface: "TreeInterfaceDownstream"): 336 | """ 337 | RPF_Interface(S) becomes I 338 | 339 | @type interface: TreeInterfaceDownstreamDownstream 340 | """ 341 | interface.join_prune_logger('is_now_RPF_Interface, P -> NI') 342 | interface.clear_prune_timer() 343 | interface.set_prune_state(DownstreamState.NoInfo) 344 | 345 | @staticmethod 346 | def send_state_refresh(interface: "TreeInterfaceDownstream"): 347 | """ 348 | Send State Refresh(S,G) out I 349 | 350 | @type interface: TreeInterfaceDownstreamDownstream 351 | """ 352 | interface.join_prune_logger.debug('send_state_refresh, P -> P') 353 | if interface.get_interface().is_state_refresh_capable(): 354 | interface.set_prune_timer(interface.get_received_prune_holdtime()) 355 | 356 | def __str__(self): 357 | return "Pruned" 358 | 359 | class DownstreamState(): 360 | NoInfo = NoInfo() 361 | Pruned = Pruned() 362 | PrunePending = PrunePending() 363 | -------------------------------------------------------------------------------- /pimdm/tree/local_membership.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class LocalMembershipStateABC(metaclass=ABCMeta): 5 | @staticmethod 6 | @abstractmethod 7 | def has_members(): 8 | raise NotImplementedError 9 | 10 | 11 | class NoInfo(LocalMembershipStateABC): 12 | @staticmethod 13 | def has_members(): 14 | return False 15 | 16 | 17 | class Include(LocalMembershipStateABC): 18 | @staticmethod 19 | def has_members(): 20 | return True 21 | 22 | 23 | class LocalMembership(): 24 | NoInfo = NoInfo() 25 | Include = Include() -------------------------------------------------------------------------------- /pimdm/tree/metric.py: -------------------------------------------------------------------------------- 1 | import ipaddress 2 | 3 | 4 | class AssertMetric(object): 5 | def __init__(self, metric_preference: int = 0x7FFFFFFF, route_metric: int = 0xFFFFFFFF, ip_address: str = "0.0.0.0", state_refresh_interval:int = None): 6 | if type(ip_address) is str: 7 | ip_address = ipaddress.ip_address(ip_address) 8 | 9 | self._metric_preference = metric_preference 10 | self._route_metric = route_metric 11 | self._ip_address = ip_address 12 | self._state_refresh_interval = state_refresh_interval 13 | 14 | def is_better_than(self, other): 15 | if self.metric_preference != other.metric_preference: 16 | return self.metric_preference < other.metric_preference 17 | elif self.route_metric != other.route_metric: 18 | return self.route_metric < other.route_metric 19 | else: 20 | return self.ip_address > other.ip_address 21 | 22 | def is_worse(self, other): 23 | return not self.is_better_than(other) 24 | 25 | def equal_metric(self, other): 26 | return self.metric_preference == other.metric_preference and self.metric_preference == other.metric_preference \ 27 | and self.ip_address == other.ip_address 28 | 29 | @staticmethod 30 | def infinite_assert_metric(): 31 | ''' 32 | @type metric: AssertMetric 33 | ''' 34 | return AssertMetric() 35 | 36 | @staticmethod 37 | def spt_assert_metric(tree_if): 38 | ''' 39 | @type metric: AssertMetric 40 | @type tree_if: TreeInterface 41 | ''' 42 | (source_ip, _) = tree_if.get_tree_id() 43 | from pimdm import UnicastRouting 44 | (metric_preference, metric_cost, _) = UnicastRouting.get_metric(source_ip) 45 | return AssertMetric(metric_preference, metric_cost, tree_if.get_ip()) 46 | 47 | 48 | def i_am_assert_winner(self, tree_if): 49 | return self.get_ip() == tree_if.get_ip() 50 | 51 | @property 52 | def metric_preference(self): 53 | return self._metric_preference 54 | 55 | @metric_preference.setter 56 | def metric_preference(self, value): 57 | self._metric_preference = value 58 | 59 | @property 60 | def route_metric(self): 61 | return self._route_metric 62 | 63 | @route_metric.setter 64 | def route_metric(self, value): 65 | self._route_metric = value 66 | 67 | 68 | @property 69 | def ip_address(self): 70 | return self._ip_address 71 | 72 | @ip_address.setter 73 | def ip_address(self, value): 74 | if type(value) is str: 75 | value = ipaddress.ip_address(value) 76 | 77 | self._ip_address = value 78 | 79 | @property 80 | def state_refresh_interval(self): 81 | return self._state_refresh_interval 82 | 83 | @state_refresh_interval.setter 84 | def state_refresh_interval(self, value): 85 | self._state_refresh_interval = value 86 | 87 | def get_ip(self): 88 | return str(self._ip_address) 89 | -------------------------------------------------------------------------------- /pimdm/tree/originator.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class OriginatorStateABC(metaclass=ABCMeta): 5 | @staticmethod 6 | @abstractmethod 7 | def recvDataMsgFromSource(tree): 8 | pass 9 | 10 | @staticmethod 11 | @abstractmethod 12 | def SRTexpires(tree): 13 | pass 14 | 15 | @staticmethod 16 | @abstractmethod 17 | def SATexpires(tree): 18 | pass 19 | 20 | @staticmethod 21 | @abstractmethod 22 | def SourceNotConnected(tree): 23 | pass 24 | 25 | 26 | class Originator(OriginatorStateABC): 27 | @staticmethod 28 | def recvDataMsgFromSource(tree): 29 | tree.set_source_active_timer() 30 | 31 | @staticmethod 32 | def SRTexpires(tree): 33 | ''' 34 | @type tree: Tree 35 | ''' 36 | tree.originator_logger.debug('SRT expired, O -> O') 37 | tree.set_state_refresh_timer() 38 | tree.create_state_refresh_msg() 39 | 40 | @staticmethod 41 | def SATexpires(tree): 42 | tree.originator_logger.debug('SAT expired, O -> NO') 43 | tree.clear_state_refresh_timer() 44 | tree.set_originator_state(OriginatorState.NotOriginator) 45 | 46 | @staticmethod 47 | def SourceNotConnected(tree): 48 | tree.originator_logger.debug('Source no longer directly connected, O -> NO') 49 | tree.clear_state_refresh_timer() 50 | tree.clear_source_active_timer() 51 | tree.set_originator_state(OriginatorState.NotOriginator) 52 | 53 | def __str__(self): 54 | return 'Originator' 55 | 56 | class NotOriginator(OriginatorStateABC): 57 | @staticmethod 58 | def recvDataMsgFromSource(tree): 59 | ''' 60 | @type interface: Tree 61 | ''' 62 | tree.originator_logger.debug('new DataMsg from Source, NO -> O') 63 | tree.set_originator_state(OriginatorState.Originator) 64 | 65 | tree.set_state_refresh_timer() 66 | tree.set_source_active_timer() 67 | 68 | @staticmethod 69 | def SRTexpires(tree): 70 | assert False, "SRTexpires in NO" 71 | 72 | @staticmethod 73 | def SATexpires(tree): 74 | assert False, "SATexpires in NO" 75 | 76 | @staticmethod 77 | def SourceNotConnected(tree): 78 | return 79 | 80 | def __str__(self): 81 | return 'NotOriginator' 82 | 83 | 84 | class OriginatorState(): 85 | NotOriginator = NotOriginator() 86 | Originator = Originator() 87 | -------------------------------------------------------------------------------- /pimdm/tree/pim_globals.py: -------------------------------------------------------------------------------- 1 | # Protocol files 2 | DAEMON_PROCESS_FILE = '/tmp/Daemon-pim{}.pid' 3 | DAEMON_SOCKET = '/tmp/pim_uds_socket{}' 4 | DAEMON_LOG_FOLDER = '/var/log/pimdm/' 5 | DAEMON_LOG_STDOUT_FILE = DAEMON_LOG_FOLDER + 'stdout{}' 6 | 7 | # PIM-DM TIMER VARIABLES 8 | ASSERT_TIME = 180 9 | GRAFT_RETRY_PERIOD = 3 10 | JP_OVERRIDE_INTERVAL = 3.0 11 | OVERRIDE_INTERVAL = 2.5 12 | PROPAGATION_DELAY = 0.5 13 | REFRESH_INTERVAL = 60 # State Refresh Interval 14 | SOURCE_LIFETIME = 210 15 | T_LIMIT = 210 16 | 17 | # PIM-DM VARIABLES 18 | HELLO_HOLD_TIME_NO_TIMEOUT = 0xFFFF 19 | HELLO_HOLD_TIME = 160 20 | HELLO_HOLD_TIME_TIMEOUT = 0 21 | 22 | ASSERT_CANCEL_METRIC = 0xFFFFFFFF 23 | 24 | # MULTIPLE TABLES SUPPORT 25 | # Define which unicast routing table to be used for RPF checks and to get route metric information 26 | # Default unicast routing table is 254 27 | UNICAST_TABLE_ID = 254 28 | # Define which multicast routing table to be used for setting multicast trees 29 | # Default multicast routing table is 0 30 | MULTICAST_TABLE_ID = 0 31 | -------------------------------------------------------------------------------- /pimdm/tree/tree_if_downstream.py: -------------------------------------------------------------------------------- 1 | from threading import Timer 2 | from pimdm.custom_timer.RemainingTimer import RemainingTimer 3 | from .assert_state import AssertState 4 | from .downstream_prune import DownstreamState, DownstreamStateABS 5 | from .tree_interface import TreeInterface 6 | from pimdm.packet.PacketPimStateRefresh import PacketPimStateRefresh 7 | from pimdm.packet.Packet import Packet 8 | from pimdm.packet.PacketPimHeader import PacketPimHeader 9 | import traceback 10 | import logging 11 | 12 | 13 | class TreeInterfaceDownstream(TreeInterface): 14 | LOGGER = logging.getLogger('pim.KernelEntry.DownstreamInterface') 15 | 16 | def __init__(self, kernel_entry, interface_id): 17 | extra_dict_logger = kernel_entry.kernel_entry_logger.extra.copy() 18 | extra_dict_logger['vif'] = interface_id 19 | extra_dict_logger['interfacename'] = kernel_entry.get_interface_name(interface_id) 20 | logger = logging.LoggerAdapter(TreeInterfaceDownstream.LOGGER, extra_dict_logger) 21 | TreeInterface.__init__(self, kernel_entry, interface_id, logger) 22 | self.logger.debug('Created DownstreamInterface') 23 | self.join_prune_logger.debug('Downstream state transitions to ' + str(self._prune_state)) 24 | 25 | # Last state refresh message sent (resend in case of new neighbors) 26 | self._last_state_refresh_message = None 27 | 28 | ########################################## 29 | # Set state 30 | ########################################## 31 | def set_prune_state(self, new_state: DownstreamStateABS): 32 | with self.get_state_lock(): 33 | if new_state != self._prune_state: 34 | self._prune_state = new_state 35 | self.join_prune_logger.debug('Downstream state transitions to ' + str(new_state)) 36 | 37 | self.change_tree() 38 | self.evaluate_ingroup() 39 | 40 | ########################################## 41 | # Check timers 42 | ########################################## 43 | def is_prune_pending_timer_running(self): 44 | return self._prune_pending_timer is not None and self._prune_pending_timer.is_alive() 45 | 46 | def is_prune_timer_running(self): 47 | return self._prune_timer is not None and self._prune_timer.is_alive() 48 | 49 | def remaining_prune_timer(self): 50 | return 0 if not self._prune_timer else self._prune_timer.time_remaining() 51 | 52 | ########################################## 53 | # Set timers 54 | ########################################## 55 | def set_prune_pending_timer(self, time): 56 | self.clear_prune_pending_timer() 57 | self._prune_pending_timer = Timer(time, self.prune_pending_timeout) 58 | self._prune_pending_timer.start() 59 | 60 | def clear_prune_pending_timer(self): 61 | if self._prune_pending_timer is not None: 62 | self._prune_pending_timer.cancel() 63 | 64 | def set_prune_timer(self, time): 65 | self.clear_prune_timer() 66 | #self._prune_timer = Timer(time, self.prune_timeout) 67 | self._prune_timer = RemainingTimer(time, self.prune_timeout) 68 | self._prune_timer.start() 69 | 70 | def clear_prune_timer(self): 71 | if self._prune_timer is not None: 72 | self._prune_timer.cancel() 73 | 74 | ########################################### 75 | # Timer timeout 76 | ########################################### 77 | def prune_pending_timeout(self): 78 | self._prune_state.PPTexpires(self) 79 | 80 | def prune_timeout(self): 81 | self._prune_state.PTexpires(self) 82 | 83 | ########################################### 84 | # Recv packets 85 | ########################################### 86 | def recv_data_msg(self): 87 | self._assert_state.receivedDataFromDownstreamIf(self) 88 | 89 | # Override 90 | def recv_prune_msg(self, upstream_neighbor_address, holdtime): 91 | super().recv_prune_msg(upstream_neighbor_address, holdtime) 92 | 93 | if upstream_neighbor_address == self.get_ip(): 94 | self.set_receceived_prune_holdtime(holdtime) 95 | self._prune_state.receivedPrune(self, holdtime) 96 | 97 | # Override 98 | def recv_join_msg(self, upstream_neighbor_address): 99 | super().recv_join_msg(upstream_neighbor_address) 100 | 101 | if upstream_neighbor_address == self.get_ip(): 102 | self._prune_state.receivedJoin(self) 103 | 104 | # Override 105 | def recv_graft_msg(self, upstream_neighbor_address, source_ip): 106 | print("GRAFT!!!") 107 | super().recv_graft_msg(upstream_neighbor_address, source_ip) 108 | 109 | if upstream_neighbor_address == self.get_ip(): 110 | self._prune_state.receivedGraft(self, source_ip) 111 | 112 | 113 | ###################################### 114 | # Send messages 115 | ###################################### 116 | def send_state_refresh(self, state_refresh_msg_received): 117 | print("send state refresh") 118 | if state_refresh_msg_received is None: 119 | return 120 | 121 | self._last_state_refresh_message = state_refresh_msg_received 122 | if self.lost_assert() or not self.get_interface().is_state_refresh_enabled(): 123 | return 124 | 125 | interval = state_refresh_msg_received.interval 126 | 127 | self._assert_state.sendStateRefresh(self, interval) 128 | self._prune_state.send_state_refresh(self) 129 | 130 | prune_indicator_bit = 0 131 | if self.is_pruned(): 132 | prune_indicator_bit = 1 133 | 134 | from pimdm import UnicastRouting 135 | (metric_preference, metric, mask) = UnicastRouting.get_metric(state_refresh_msg_received.source_address) 136 | 137 | assert_override_flag = 0 138 | if self._assert_state == AssertState.NoInfo: 139 | assert_override_flag = 1 140 | 141 | try: 142 | ph = PacketPimStateRefresh(multicast_group_adress=state_refresh_msg_received.multicast_group_adress, 143 | source_address=state_refresh_msg_received.source_address, 144 | originator_adress=state_refresh_msg_received.originator_adress, 145 | metric_preference=metric_preference, metric=metric, mask_len=mask, 146 | ttl=state_refresh_msg_received.ttl - 1, 147 | prune_indicator_flag=prune_indicator_bit, 148 | prune_now_flag=state_refresh_msg_received.prune_now_flag, 149 | assert_override_flag=assert_override_flag, 150 | interval=interval) 151 | pckt = Packet(payload=PacketPimHeader(ph)) 152 | 153 | self.get_interface().send(pckt.bytes()) 154 | except: 155 | traceback.print_exc() 156 | return 157 | 158 | 159 | ########################################################## 160 | 161 | # Override 162 | def is_forwarding(self): 163 | return ((self.has_neighbors() and not self.is_pruned()) or self.igmp_has_members()) and not self.lost_assert() 164 | 165 | def is_pruned(self): 166 | return self._prune_state == DownstreamState.Pruned 167 | 168 | #def lost_assert(self): 169 | # return not AssertMetric.i_am_assert_winner(self) and \ 170 | # self._assert_winner_metric.is_better_than(AssertMetric.spt_assert_metric(self)) 171 | 172 | # Override 173 | # When new neighbor connects, send last state refresh msg 174 | def new_or_reset_neighbor(self, neighbor_ip): 175 | self.send_state_refresh(self._last_state_refresh_message) 176 | 177 | 178 | # Override 179 | def delete(self, change_type_interface=False): 180 | super().delete(change_type_interface) 181 | self.clear_assert_timer() 182 | self.clear_prune_timer() 183 | self.clear_prune_pending_timer() 184 | 185 | def is_downstream(self): 186 | return True 187 | -------------------------------------------------------------------------------- /pimdm/tree/tree_if_upstream.py: -------------------------------------------------------------------------------- 1 | from .tree_interface import TreeInterface 2 | from .upstream_prune import UpstreamState 3 | from threading import Timer 4 | from pimdm.custom_timer.RemainingTimer import RemainingTimer 5 | from .pim_globals import * 6 | import random 7 | from .metric import AssertMetric 8 | from .originator import OriginatorState, OriginatorStateABC 9 | from pimdm.packet.PacketPimStateRefresh import PacketPimStateRefresh 10 | import traceback 11 | from . import data_packets_socket 12 | import threading 13 | import logging 14 | 15 | 16 | class TreeInterfaceUpstream(TreeInterface): 17 | LOGGER = logging.getLogger('pim.KernelEntry.UpstreamInterface') 18 | 19 | def __init__(self, kernel_entry, interface_id): 20 | extra_dict_logger = kernel_entry.kernel_entry_logger.extra.copy() 21 | extra_dict_logger['vif'] = interface_id 22 | extra_dict_logger['interfacename'] = kernel_entry.get_interface_name(interface_id) 23 | logger = logging.LoggerAdapter(TreeInterfaceUpstream.LOGGER, extra_dict_logger) 24 | TreeInterface.__init__(self, kernel_entry, interface_id, logger) 25 | 26 | # Graft/Prune State: 27 | self._graft_prune_state = UpstreamState.Forward 28 | self._graft_retry_timer = None 29 | self._override_timer = None 30 | self._prune_limit_timer = None 31 | self._last_rpf = self.get_neighbor_RPF() 32 | self.join_prune_logger.debug('Upstream state transitions to ' + str(self._graft_prune_state)) 33 | 34 | # Originator state 35 | self._originator_state = OriginatorState.NotOriginator 36 | self._state_refresh_timer = None 37 | self._source_active_timer = None 38 | self._prune_now_counter = 0 39 | self.originator_logger = logging.LoggerAdapter(TreeInterfaceUpstream.LOGGER.getChild('Originator'), extra_dict_logger) 40 | self.originator_logger.debug('StateRefresh state transitions to ' + str(self._originator_state)) 41 | 42 | if self.is_S_directly_conn(): 43 | self._graft_prune_state.sourceIsNowDirectConnect(self) 44 | interface = self.get_interface() 45 | if interface is not None and interface.is_state_refresh_enabled(): 46 | self._originator_state.recvDataMsgFromSource(self) 47 | 48 | 49 | # TODO TESTE SOCKET RECV DATA PCKTS 50 | self.socket_is_enabled = True 51 | (s, g) = self.get_tree_id() 52 | interface_name = self.get_interface_name() 53 | self.socket_pkt = data_packets_socket.get_s_g_bpf_filter_code(s, g, interface_name) 54 | 55 | # run receive method in background 56 | receive_thread = threading.Thread(target=self.socket_recv) 57 | receive_thread.daemon = True 58 | receive_thread.start() 59 | 60 | self.logger.debug('Created UpstreamInterface') 61 | 62 | def socket_recv(self): 63 | while self.socket_is_enabled: 64 | try: 65 | self.socket_pkt.recvfrom(0) 66 | print("DATA RECEIVED") 67 | self.recv_data_msg() 68 | except: 69 | traceback.print_exc() 70 | continue 71 | 72 | ########################################## 73 | # Set state 74 | ########################################## 75 | def set_state(self, new_state): 76 | with self.get_state_lock(): 77 | if new_state != self._graft_prune_state: 78 | self._graft_prune_state = new_state 79 | self.join_prune_logger.debug('Upstream state transitions to ' + str(new_state)) 80 | 81 | self.change_tree() 82 | self.evaluate_ingroup() 83 | 84 | def set_originator_state(self, new_state: OriginatorStateABC): 85 | if new_state != self._originator_state: 86 | self._originator_state = new_state 87 | self.originator_logger.debug('StateRefresh state transitions to ' + str(new_state)) 88 | 89 | ########################################## 90 | # Check timers 91 | ########################################## 92 | def is_graft_retry_timer_running(self): 93 | return self._graft_retry_timer is not None and self._graft_retry_timer.is_alive() 94 | 95 | def is_override_timer_running(self): 96 | return self._override_timer is not None and self._override_timer.is_alive() 97 | 98 | def is_prune_limit_timer_running(self): 99 | return self._prune_limit_timer is not None and self._prune_limit_timer.is_alive() 100 | 101 | def remaining_prune_limit_timer(self): 102 | return 0 if not self._prune_limit_timer else self._prune_limit_timer.time_remaining() 103 | 104 | ########################################## 105 | # Set timers 106 | ########################################## 107 | def set_graft_retry_timer(self, time=GRAFT_RETRY_PERIOD): 108 | self.clear_graft_retry_timer() 109 | self._graft_retry_timer = Timer(time, self.graft_retry_timeout) 110 | self._graft_retry_timer.start() 111 | 112 | def clear_graft_retry_timer(self): 113 | if self._graft_retry_timer is not None: 114 | self._graft_retry_timer.cancel() 115 | 116 | def set_override_timer(self): 117 | self.clear_override_timer() 118 | self._override_timer = Timer(self.t_override, self.override_timeout) 119 | self._override_timer.start() 120 | 121 | def clear_override_timer(self): 122 | if self._override_timer is not None: 123 | self._override_timer.cancel() 124 | 125 | def set_prune_limit_timer(self, time=T_LIMIT): 126 | self.clear_prune_limit_timer() 127 | self._prune_limit_timer = RemainingTimer(time, self.prune_limit_timeout) 128 | self._prune_limit_timer.start() 129 | 130 | def clear_prune_limit_timer(self): 131 | if self._prune_limit_timer is not None: 132 | self._prune_limit_timer.cancel() 133 | 134 | # State Refresh timers 135 | def set_state_refresh_timer(self): 136 | self.clear_state_refresh_timer() 137 | self._state_refresh_timer = Timer(REFRESH_INTERVAL, self.state_refresh_timeout) 138 | self._state_refresh_timer.start() 139 | 140 | def clear_state_refresh_timer(self): 141 | if self._state_refresh_timer is not None: 142 | self._state_refresh_timer.cancel() 143 | 144 | def set_source_active_timer(self): 145 | self.clear_source_active_timer() 146 | self._source_active_timer = Timer(SOURCE_LIFETIME, self.source_active_timeout) 147 | self._source_active_timer.start() 148 | 149 | def clear_source_active_timer(self): 150 | if self._source_active_timer is not None: 151 | self._source_active_timer.cancel() 152 | 153 | ########################################### 154 | # Timer timeout 155 | ########################################### 156 | def graft_retry_timeout(self): 157 | self._graft_prune_state.GRTexpires(self) 158 | 159 | def override_timeout(self): 160 | self._graft_prune_state.OTexpires(self) 161 | 162 | def prune_limit_timeout(self): 163 | return 164 | 165 | # State Refresh timers 166 | def state_refresh_timeout(self): 167 | self._originator_state.SRTexpires(self) 168 | 169 | def source_active_timeout(self): 170 | self._originator_state.SATexpires(self) 171 | 172 | ########################################### 173 | # Recv packets 174 | ########################################### 175 | def recv_data_msg(self): 176 | if not self.is_prune_limit_timer_running() and not self.is_S_directly_conn() and self.is_olist_null(): 177 | self._graft_prune_state.dataArrivesRPFinterface_OListNull_PLTstoped(self) 178 | elif self.is_S_directly_conn(): 179 | interface = self.get_interface() 180 | if interface is not None and interface.is_state_refresh_enabled(): 181 | self._originator_state.recvDataMsgFromSource(self) 182 | 183 | def recv_join_msg(self, upstream_neighbor_address): 184 | super().recv_join_msg(upstream_neighbor_address) 185 | if upstream_neighbor_address == self.get_neighbor_RPF(): 186 | self._graft_prune_state.seeJoinToRPFnbr(self) 187 | 188 | def recv_prune_msg(self, upstream_neighbor_address, holdtime): 189 | super().recv_prune_msg(upstream_neighbor_address, holdtime) 190 | self.set_receceived_prune_holdtime(holdtime) 191 | self._graft_prune_state.seePrune(self) 192 | 193 | def recv_graft_ack_msg(self, source_ip_of_graft_ack): 194 | print("GRAFT ACK!!!") 195 | if source_ip_of_graft_ack == self.get_neighbor_RPF(): 196 | self._graft_prune_state.recvGraftAckFromRPFnbr(self) 197 | 198 | def recv_state_refresh_msg(self, received_metric: AssertMetric, prune_indicator: int): 199 | super().recv_state_refresh_msg(received_metric, prune_indicator) 200 | 201 | if self.get_neighbor_RPF() != received_metric.get_ip(): 202 | return 203 | if prune_indicator == 1: 204 | self._graft_prune_state.stateRefreshArrivesRPFnbr_pruneIs1(self) 205 | elif prune_indicator == 0 and not self.is_prune_limit_timer_running(): 206 | self._graft_prune_state.stateRefreshArrivesRPFnbr_pruneIs0_PLTstoped(self) 207 | 208 | #################################### 209 | def create_state_refresh_msg(self): 210 | self._prune_now_counter+=1 211 | (source_ip, group_ip) = self.get_tree_id() 212 | ph = PacketPimStateRefresh(multicast_group_adress=group_ip, 213 | source_address=source_ip, 214 | originator_adress=self.get_ip(), 215 | metric_preference=0, metric=0, mask_len=0, 216 | ttl=256, 217 | prune_indicator_flag=0, 218 | prune_now_flag=self._prune_now_counter//3, 219 | assert_override_flag=0, 220 | interval=60) 221 | 222 | self._prune_now_counter %= 3 223 | self._kernel_entry.forward_state_refresh_msg(ph) 224 | 225 | ########################################### 226 | # Change olist 227 | ########################################### 228 | def olist_is_null(self): 229 | self._graft_prune_state.olistIsNowNull(self) 230 | 231 | def olist_is_not_null(self): 232 | self._graft_prune_state.olistIsNowNotNull(self) 233 | 234 | ########################################### 235 | # Changes to RPF'(s) 236 | ########################################### 237 | 238 | # caused by assert transition: 239 | def set_assert_state(self, new_state): 240 | super().set_assert_state(new_state) 241 | self.change_rpf(self.is_olist_null()) 242 | 243 | # caused by unicast routing table: 244 | def change_on_unicast_routing(self, interface_change=False): 245 | self.change_rpf(self.is_olist_null(), interface_change) 246 | 247 | ''' 248 | if self.is_S_directly_conn(): 249 | self._graft_prune_state.sourceIsNowDirectConnect(self) 250 | else: 251 | self._originator_state.SourceNotConnected(self) 252 | ''' 253 | 254 | def change_rpf(self, olist_is_null, interface_change=False): 255 | current_rpf = self.get_neighbor_RPF() 256 | if interface_change or self._last_rpf != current_rpf: 257 | self._last_rpf = current_rpf 258 | if olist_is_null: 259 | self._graft_prune_state.RPFnbrChanges_olistIsNull(self) 260 | else: 261 | self._graft_prune_state.RPFnbrChanges_olistIsNotNull(self) 262 | 263 | #################################################################### 264 | #Override 265 | def is_forwarding(self): 266 | return False 267 | 268 | # If new/reset neighbor is RPF neighbor => clear prune limit timer 269 | def new_or_reset_neighbor(self, neighbor_ip): 270 | if neighbor_ip == self.get_neighbor_RPF(): 271 | self.clear_prune_limit_timer() 272 | 273 | #Override 274 | def delete(self, change_type_interface=False): 275 | self.socket_is_enabled = False 276 | self.socket_pkt.close() 277 | super().delete(change_type_interface) 278 | self.clear_graft_retry_timer() 279 | self.clear_assert_timer() 280 | self.clear_prune_limit_timer() 281 | self.clear_override_timer() 282 | self.clear_state_refresh_timer() 283 | self.clear_source_active_timer() 284 | 285 | # Clear Graft/Prune State: 286 | self._graft_prune_state = None 287 | 288 | # Clear Originator state 289 | self._originator_state = None 290 | 291 | def is_downstream(self): 292 | return False 293 | 294 | def is_originator(self): 295 | return self._originator_state == OriginatorState.Originator 296 | 297 | ######################################################################### 298 | # Properties 299 | ######################################################################### 300 | @property 301 | def t_override(self): 302 | oi = self.get_interface()._override_interval 303 | return random.uniform(0, oi) 304 | -------------------------------------------------------------------------------- /pimdm/utils.py: -------------------------------------------------------------------------------- 1 | import array 2 | 3 | 4 | def checksum(pkt: bytes) -> bytes: 5 | if len(pkt) % 2 == 1: 6 | pkt += "\0" 7 | s = sum(array.array("H", pkt)) 8 | s = (s >> 16) + (s & 0xffff) 9 | s += s >> 16 10 | s = ~s 11 | return (((s >> 8) & 0xff) | s << 8) & 0xffff 12 | 13 | 14 | # obtain TYPE_CHECKING (for type hinting) 15 | try: 16 | from typing import TYPE_CHECKING 17 | except ImportError: 18 | TYPE_CHECKING = False 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PrettyTable 2 | netifaces 3 | ipaddress 4 | pyroute2 5 | py-mld==1.0.3 6 | igmp==1.0.4 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from setuptools import setup, find_packages, Extension 3 | 4 | try: 5 | from Cython.Build import cythonize 6 | except ModuleNotFoundError: 7 | raise SystemExit("Cython is required. You can install it with pip.") 8 | 9 | 10 | # we only support Python 3 version >= 3.3 11 | if len(sys.argv) >= 2 and sys.argv[1] == "install" and sys.version_info < (3, 3): 12 | raise SystemExit("Python 3.3 or higher is required") 13 | 14 | 15 | setup( 16 | name="pim-dm", 17 | description="PIM-DM protocol", 18 | long_description=open("README.md", "r").read(), 19 | long_description_content_type="text/markdown", 20 | keywords="PIM-DM Multicast Routing Protocol PIM Dense-Mode Router RFC3973 IPv4 IPv6", 21 | version="1.4.0", 22 | url="http://github.com/pedrofran12/pim_dm", 23 | author="Pedro Oliveira", 24 | author_email="pedro.francisco.oliveira@tecnico.ulisboa.pt", 25 | license="MIT", 26 | install_requires=[ 27 | 'PrettyTable', 28 | 'netifaces', 29 | 'ipaddress', 30 | 'pyroute2', 31 | 'py-mld==1.0.3', 32 | 'igmp==1.0.4', 33 | ], 34 | packages=find_packages(exclude=["docs"]), 35 | ext_modules = cythonize([ 36 | Extension("pcap_wrapper", ["pcap.pyx"], 37 | libraries=["pcap"]), 38 | ], language_level=3), 39 | entry_points={ 40 | "console_scripts": [ 41 | "pim-dm = pimdm.Run:main", 42 | ] 43 | }, 44 | include_package_data=True, 45 | zip_safe=False, 46 | platforms="any", 47 | classifiers=[ 48 | "Development Status :: 4 - Beta", 49 | "Environment :: Console", 50 | "Intended Audience :: Information Technology", 51 | "Topic :: System :: Networking", 52 | "License :: OSI Approved :: MIT License", 53 | "Natural Language :: English", 54 | "Operating System :: OS Independent", 55 | "Programming Language :: Python", 56 | "Programming Language :: Python :: 3", 57 | "Programming Language :: Python :: 3.3", 58 | "Programming Language :: Python :: 3.4", 59 | "Programming Language :: Python :: 3.5", 60 | "Programming Language :: Python :: 3.6", 61 | "Programming Language :: Python :: 3.7", 62 | "Programming Language :: Python :: 3.8", 63 | "Programming Language :: Python :: 3.9", 64 | "Programming Language :: Python :: 3.10", 65 | ], 66 | python_requires=">=3.3", 67 | ) 68 | --------------------------------------------------------------------------------