Import upstream version 3.0.1
Kali Janitor
2 years ago
0 | 0 | # Changelog |
1 | 1 | |
2 | 2 | All notable changes to this project will be documented in this file. |
3 | ||
4 | ## [v3.0.1](https://github.com/ValentinBELYN/icmplib/releases/tag/v3.0.1) - 2021-08-14 | |
5 | - Make the `SocketPermissionError` more explicit when the OS does not allow instantiation of unprivileged sockets. | |
6 | - Delete unnecessary properties from exceptions. | |
7 | - Improve SEO on GitHub. | |
8 | ||
9 | ## [v3.0.0](https://github.com/ValentinBELYN/icmplib/releases/tag/v3.0.0) - 2021-06-01 | |
10 | **icmplib 3.0 is here! :rocket:** | |
11 | ||
12 | - The library is now asynchronous! | |
13 | - Introduce new functions: `async_ping`, `async_multiping` and `async_resolve`. | |
14 | - Add a new `AsyncSocket` class to make an ICMP socket asynchronous. | |
15 | - Rewrite models: | |
16 | - All the properties of `Host` and `Hop` classes are now lazy. | |
17 | - Add new properties to the `Host` and `Hop` classes: you can retrieve the list of round-trip times and calculate the jitter. | |
18 | - Add the metaclass `__str__` to visualize the results more easily. | |
19 | - Rewrite the `ping`, `multiping` and `traceroute` functions: | |
20 | - The identifier of ICMP requests is now handled automatically by the library. | |
21 | - Add the ability to set the address family if a hostname or an FQDN is specified. | |
22 | - A new implementation is used for the `multiping` function. It should be more reliable. | |
23 | - The `multiping` function is now in a dedicated module. | |
24 | - Rewrite the documentation to help you get started with icmplib. | |
25 | - Improve the `resolve` function to return all IP addresses found. | |
26 | - Add the `is_hostname` function to check if a string is a hostname or an FQDN. | |
27 | - Delete the experimental class `BufferedSocket`, replaced by `AsyncSocket`. | |
28 | - Performance and compatibility improvement. | |
29 | - Many other small changes and fixes. | |
30 | ||
31 | Special thanks to [@JonasKs](https://github.com/JonasKs) and [@bdraco](https://github.com/bdraco) for their suggestions, advice and feedback! | |
32 | ||
33 | > Python 3.7 or later is now required. | |
34 | ||
35 | ## [v2.1.1](https://github.com/ValentinBELYN/icmplib/releases/tag/v2.1.1) - 2021-03-21 | |
36 | - :bug: Revert changes made to the `traceroute` function due to a bug. | |
37 | ||
38 | > This version is the last of the 2.x branch. See you soon for the release of icmplib 3.0! | |
39 | ||
40 | ## [v2.1.0](https://github.com/ValentinBELYN/icmplib/releases/tag/v2.1.0) - 2021-03-20 | |
41 | - Add a `family` parameter to the `resolve` function to define the address family. | |
42 | - Improve the reliability of the results of the `traceroute` function. | |
43 | ||
44 | ## [v2.0.2](https://github.com/ValentinBELYN/icmplib/releases/tag/v2.0.2) - 2021-02-07 | |
45 | - Rename the default branch from `master` to `main`. | |
46 | - Add more details about the `privileged` parameter in the `README` file (part 2). | |
47 | - :bug: Fix a bug preventing the `traceroute` function to work with IPv6 addresses (part 2). | |
48 | ||
49 | ## [v2.0.1](https://github.com/ValentinBELYN/icmplib/releases/tag/v2.0.1) - 2020-12-12 | |
50 | - Handle `EACCES` errors at sockets level. | |
51 | - Add some details about the `privileged` parameter in the `README` file. | |
52 | - :bug: Fix a bug preventing the `traceroute` function to work with IPv6 addresses. | |
53 | ||
54 | ## [v2.0.0](https://github.com/ValentinBELYN/icmplib/releases/tag/v2.0.0) - 2020-11-15 | |
55 | **icmplib 2.0 is here! :rocket:** | |
56 | ||
57 | - New library architecture. | |
58 | - Add a new `multiping` function. This function will be faster and more memory efficient. | |
59 | - Add the ability to use the library without root privileges. | |
60 | - Add the ability to set a source IP address for sending your ICMP packets. | |
61 | - Add a `first_hop` parameter to the `traceroute` function to set the initial time to live value ([@scoulondre](https://github.com/scoulondre)). | |
62 | - Add two new exceptions: | |
63 | - `NameLookupError`: raised when the requested name does not exist or cannot be resolved. | |
64 | - `SocketAddressError`: raised when the requested address cannot be assigned to the socket. | |
65 | - Add a new `BufferedSocket` class (experimental) to send several packets simultaneously without waiting for a response. | |
66 | - The `receive` method of sockets can receive all incoming packets. | |
67 | - Throw new exceptions when instantiating sockets, sending and receiving packets. | |
68 | - Improve the `resolve` function: | |
69 | - A `NameLookupError` is now raised if the requested name does not exist or cannot be resolved. | |
70 | - Improve compatibility with Linux, macOS and Windows. | |
71 | - Delete deprecated properties. | |
72 | - Update docstrings, examples and documentation. | |
73 | ||
74 | > Compatibility with existing programs is maintained. | |
3 | 75 | |
4 | 76 | ## [v1.2.2](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.2.2) - 2020-10-10 |
5 | 77 | - Add support for hostnames and FQDN resolution to IPv6 addresses. |
6 | 78 | - Performance improvement. |
7 | 79 | |
8 | 80 | ## [v1.2.1](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.2.1) - 2020-09-26 |
9 | - Fix an issue in the `traceroute` function which gave the wrong value for the `avg_rtt` property. | |
10 | - Some other tweaks to the `traceroute` function. | |
81 | - :bug: Fix an issue in the `traceroute` function which gave the wrong value for the `avg_rtt` property ([@patrickfnielsen](https://github.com/patrickfnielsen)). | |
11 | 82 | |
12 | 83 | ## [v1.2.0](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.2.0) - 2020-09-12 |
13 | 84 | - Add the ability to modify the traffic class of ICMP packets. |
15 | 86 | - Add a new exception `SocketUnavailableError` when an action is performed while a socket is closed. |
16 | 87 | - Add a warning message on deprecated properties. |
17 | 88 | - Explicit closure of sockets on built-in functions. |
18 | - Fix a bug when ICMP responses are not correctly formatted (part 2). | |
89 | - :bug: Fix a bug when ICMP responses are not correctly formatted (part 2). | |
19 | 90 | |
20 | 91 | ## [v1.1.3](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.1.3) - 2020-09-03 |
21 | - Fix a bug when ICMP responses are not correctly formatted. | |
92 | - :bug: Fix a bug when ICMP responses are not correctly formatted. | |
22 | 93 | |
23 | 94 | ## [v1.1.2](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.1.2) - 2020-08-29 |
24 | - Fix a compatibility issue. | |
95 | - :bug: Fix a compatibility issue. | |
25 | 96 | |
26 | 97 | ## [v1.1.1](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.1.1) - 2020-07-10 |
27 | - Fix a bug when the source host does not have an IP address. | |
98 | - :bug: Fix a bug when the source host does not have an IP address. | |
28 | 99 | |
29 | 100 | ## [v1.1.0](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.1.0) - 2020-06-25 |
101 | - Add support for odd size payloads. | |
30 | 102 | - Normalize the names of variables and properties: |
31 | 103 | - `ICMPReply` class: the `received_bytes` property is deprecated. Use `bytes_received` instead. |
32 | 104 | - `Host` and `Hop` classes: the `transmitted_packets` property is deprecated. Use `packets_sent` instead. |
33 | 105 | - `Host` and `Hop` classes: the `received_packets` property is deprecated. Use `packets_received` instead. |
34 | 106 | - Normalize docstrings. |
35 | - Add support for odd size payloads. | |
36 | 107 | - Optimizations. |
37 | 108 | |
38 | 109 | ## [v1.0.4](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.0.4) - 2020-06-14 |
39 | 110 | - Add the `is_closed` property to the `ICMPSocket` class. |
40 | 111 | - Round round-trip time values by default. |
41 | - Fix a bug in the `multiping` function: the `id` parameter was ignored. | |
42 | - Fix a bug in the `ICMPSocket` class when instantiated without root privileges. | |
43 | 112 | - Add an index for examples. |
113 | - :bug: Fix a bug in the `multiping` function where the `id` parameter was ignored. | |
114 | - :bug: Fix a bug in the `ICMPSocket` class when instantiated without root privileges. | |
44 | 115 | |
45 | 116 | ## [v1.0.3](https://github.com/ValentinBELYN/icmplib/releases/tag/v1.0.3) - 2020-05-09 |
46 | 117 | - Add the ability to customize the payload. |
47 | - Improvements of `ping` and `multiping` functions. It is now possible to pass arguments to the `ICMPRequest` object using keywords arguments `**kwargs`. | |
118 | - Improve the `ping` and `multiping` functions: | |
119 | - You can pass arguments to the `ICMPRequest` object using keywords arguments `**kwargs`. | |
48 | 120 | - Update some docstrings. |
49 | 121 | - Add new examples. |
50 | 122 |
0 | 0 | <div align="center"> |
1 | <br> | |
1 | 2 | <br> |
2 | 3 | <img src="media/icmplib-logo.png" height="125" width="100" alt="icmplib"> |
3 | 4 | <br> |
5 | ||
4 | 6 | <br> |
5 | ||
6 | <p><strong>Easily forge ICMP packets and make your own ping and traceroute.</strong></p> | |
7 | <a href="https://pypi.org/project/icmplib"> | |
8 | <img src="https://img.shields.io/pypi/dm/icmplib.svg?style=flat-square&labelColor=0366d6&color=005cc5" alt="statistics"> | |
9 | </a> | |
10 | <br> | |
11 | <br> | |
12 | ||
13 | 7 | <div> |
14 | 8 | <a href="#features">Features</a> |
15 | 9 | <a href="#installation">Installation</a> |
16 | <a href="#built-in-functions">Built-in functions</a> | |
17 | <a href="#icmp-sockets">ICMP sockets</a> | |
18 | <a href="#faq">FAQ</a> | |
10 | <a href="#getting-started">Getting started</a> | |
11 | <a href="#documentation">Documentation</a> | |
19 | 12 | <a href="#contributing">Contributing</a> |
20 | 13 | <a href="#donate">Donate</a> |
21 | 14 | <a href="#license">License</a> |
22 | 15 | </div> |
23 | 16 | <br> |
24 | <br> | |
25 | 17 | |
26 | 18 | <pre>icmplib is a brand new and modern implementation of the ICMP protocol in Python. |
27 | 19 | Use the built-in functions or build your own, you have the choice! |
28 | 20 | |
29 | <strong>Root privileges are required to use this library (raw sockets).</strong></pre> | |
21 | <a href="CHANGELOG.md">icmplib 3.0 has been released! See what's new 🎉</a></pre> | |
22 | ||
23 | <a href="https://pypi.org/project/icmplib"> | |
24 | <img src="https://img.shields.io/pypi/dm/icmplib.svg?style=flat-square&labelColor=0366d6&color=005cc5" alt="statistics"> | |
25 | </a> | |
30 | 26 | </div> |
31 | 27 | |
32 | 28 | <br> |
33 | 29 | |
34 | 30 | ## Features |
35 | 31 | |
36 | - :deciduous_tree: **Ready-to-use:** icmplib offers ready-to-use functions such as the most popular ones: `ping`, `multiping` and `traceroute`. | |
37 | - :gem: **Modern:** This library uses the latest technologies offered by Python 3.6+ and is fully object-oriented. | |
38 | - :rocket: **Fast:** Each class and function has been designed and optimized to deliver the best performance. Some functions are also multithreaded (like the `multiping` function). You can ping the world in seconds! | |
39 | - :nut_and_bolt: **Powerful and evolutive:** Easily build your own classes and functions with `ICMPv4` and `ICMPv6` sockets. | |
40 | - :fire: **Seamless integration of IPv6:** Use IPv6 the same way you use IPv4. Automatic detection is done without impacting performance. | |
41 | - :rainbow: **Broadcast support** (you must use the `ICMPv4Socket` class to enable it). | |
42 | - :beer: **Support of all operating systems.** Tested on Linux, macOS and Windows. | |
43 | - :metal: **No dependency:** icmplib is a pure Python implementation of the ICMP protocol. It does not use any external dependencies. | |
32 | - :deciduous_tree: **Ready-to-use:** icmplib offers ready-to-use functions such as the most popular ones: `ping`, `multiping` and `traceroute`. An extensive documentation also helps you get started. | |
33 | - :gem: **Modern:** This library uses the latest mechanisms offered by Python 3.6/3.7+ and is fully object-oriented. | |
34 | - :rocket: **Fast:** Each class and function has been designed and optimized to deliver the best performance. Some functions are also asynchronous like the `async_ping` and `async_multiping` functions. You can ping the world in seconds! | |
35 | - :zap: **Powerful:** Use the library without root privileges, set the traffic class of ICMP packets, customize their payload, send broadcast requests and more! | |
36 | - :nut_and_bolt: **Evolutive:** Easily build your own classes and functions with `ICMPv4` and `ICMPv6` sockets. | |
37 | - :fire: **Seamless integration of IPv6:** Use IPv6 the same way you use IPv4. | |
38 | - :beer: **Cross-platform:** Optimized for Linux, macOS and Windows. The library automatically manages the specificities of each system. | |
39 | - :metal: **No dependency:** icmplib is a pure Python implementation of the ICMP protocol. It does not rely on any external dependency. | |
44 | 40 | |
45 | 41 | <br> |
46 | 42 | |
47 | 43 | ## Installation |
48 | 44 | |
49 | Install, upgrade and uninstall icmplib with these commands: | |
50 | ||
51 | ```shell | |
52 | $ pip3 install icmplib | |
53 | $ pip3 install --upgrade icmplib | |
54 | $ pip3 uninstall icmplib | |
55 | ``` | |
56 | ||
57 | icmplib requires Python 3.6 or later. | |
58 | ||
59 | Import icmplib into your project (only import what you need): | |
60 | ||
61 | ```python | |
62 | # For simple use | |
63 | from icmplib import ping, multiping, traceroute, Host, Hop | |
64 | ||
65 | # For advanced use (sockets) | |
66 | from icmplib import ICMPv4Socket, ICMPv6Socket, ICMPRequest, ICMPReply | |
67 | ||
68 | # Exceptions | |
69 | from icmplib import ICMPLibError, ICMPSocketError, SocketPermissionError | |
70 | from icmplib import SocketUnavailableError, SocketBroadcastError, TimeoutExceeded | |
71 | from icmplib import ICMPError, DestinationUnreachable, TimeExceeded | |
72 | ``` | |
73 | ||
74 | <br> | |
75 | ||
76 | ## Built-in functions | |
77 | ||
78 | ### Ping | |
79 | Send *ICMP ECHO_REQUEST* packets to a network host. | |
80 | ||
81 | #### Definition | |
82 | ```python | |
83 | ping(address, count=4, interval=1, timeout=2, id=PID, **kwargs) | |
45 | - **Install icmplib** | |
46 | ||
47 | The recommended way to install icmplib is to use `pip3`: | |
48 | ||
49 | ```shell | |
50 | $ pip3 install icmplib | |
51 | ``` | |
52 | ||
53 | *Since icmplib 3, Python 3.7 or later is required to use the library.*<br> | |
54 | *If you are using Python 3.6 and you cannot update it, you can still install icmplib 2.* | |
55 | ||
56 | - **Import basic functions** | |
57 | ||
58 | ```python | |
59 | from icmplib import ping, multiping, traceroute, resolve | |
60 | ``` | |
61 | ||
62 | - **Import asynchronous functions** | |
63 | ||
64 | ```python | |
65 | from icmplib import async_ping, async_multiping, async_resolve | |
66 | ``` | |
67 | ||
68 | - **Import sockets (advanced)** | |
69 | ||
70 | ```python | |
71 | from icmplib import ICMPv4Socket, ICMPv6Socket, AsyncSocket, ICMPRequest, ICMPReply | |
72 | ``` | |
73 | ||
74 | - **Import exceptions** | |
75 | ||
76 | ```python | |
77 | from icmplib import ICMPLibError, NameLookupError, ICMPSocketError | |
78 | from icmplib import SocketAddressError, SocketPermissionError | |
79 | from icmplib import SocketUnavailableError, SocketBroadcastError, TimeoutExceeded | |
80 | from icmplib import ICMPError, DestinationUnreachable, TimeExceeded | |
81 | ``` | |
82 | ||
83 | *Import only what you need.* | |
84 | ||
85 | <br> | |
86 | ||
87 | ## Getting started | |
88 | ||
89 | ### ping | |
90 | ||
91 | Send ICMP Echo Request packets to a network host. | |
92 | ||
93 | ```python | |
94 | ping(address, count=4, interval=1, timeout=2, id=None, source=None, family=None, privileged=True, **kwargs) | |
84 | 95 | ``` |
85 | 96 | |
86 | 97 | #### Parameters |
98 | ||
87 | 99 | - `address` |
88 | 100 | |
89 | The IP address of the gateway or host to which the message should be sent. | |
101 | The IP address, hostname or FQDN of the host to which messages should be sent. For deterministic behavior, prefer to use an IP address. | |
90 | 102 | |
91 | 103 | - Type: `str` |
92 | 104 | |
113 | 125 | |
114 | 126 | - `id` |
115 | 127 | |
116 | The identifier of the request. Used to match the reply with the request.<br> | |
117 | In practice, a unique identifier is used for every ping process. | |
118 | ||
119 | - Type: `int` | |
120 | - Default: `PID` | |
121 | ||
122 | - `**kwargs` | |
123 | ||
124 | `Optional` Advanced use: arguments passed to `ICMPRequest` objects. | |
128 | The identifier of ICMP requests. Used to match the responses with requests. In practice, a unique identifier should be used for every ping process. On Linux, this identifier is ignored when the `privileged` parameter is disabled. The library handles this identifier itself by default. | |
129 | ||
130 | - Type: `int` | |
131 | - Default: `None` | |
132 | ||
133 | - `source` | |
134 | ||
135 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destination. | |
136 | ||
137 | - Type: `str` | |
138 | - Default: `None` | |
139 | ||
140 | - `family` | |
141 | ||
142 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
143 | ||
144 | - Type: `int` | |
145 | - Default: `None` | |
146 | ||
147 | - `privileged` | |
148 | ||
149 | When this option is enabled, this library fully manages the exchanges and the structure of ICMP packets. Disable this option if you want to use this function without root privileges and let the kernel handle ICMP headers. | |
150 | ||
151 | [Learn more about the `privileged` parameter.] | |
152 | ||
153 | *Only available on Unix systems. Ignored on Windows.* | |
154 | ||
155 | - Type: `bool` | |
156 | - Default: `True` | |
157 | ||
158 | - `payload` | |
159 | ||
160 | The payload content in bytes. A random payload is used by default. | |
161 | ||
162 | - Type: `bytes` | |
163 | - Default: `None` | |
164 | ||
165 | - `payload_size` | |
166 | ||
167 | The payload size. Ignored when the `payload` parameter is set. | |
168 | ||
169 | - Type: `int` | |
170 | - Default: `56` | |
171 | ||
172 | - `traffic_class` | |
173 | ||
174 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
175 | ||
176 | *Only available on Unix systems. Ignored on Windows.* | |
177 | ||
178 | - Type: `int` | |
179 | - Default: `0` | |
125 | 180 | |
126 | 181 | #### Return value |
127 | - `Host` object | |
128 | ||
129 | A `Host` object containing statistics about the desired destination:<br> | |
130 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `packets_sent`,<br> | |
131 | `packets_received`, `packet_loss`, `is_alive`. | |
182 | ||
183 | - A `Host` object containing statistics about the desired destination:<br> | |
184 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive` | |
132 | 185 | |
133 | 186 | #### Exceptions |
134 | - `SocketPermissionError` | |
135 | ||
136 | If the permissions are insufficient to create a socket. | |
187 | ||
188 | - [`NameLookupError`] | |
189 | ||
190 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
191 | ||
192 | - [`SocketPermissionError`] | |
193 | ||
194 | If the privileges are insufficient to create the socket. | |
195 | ||
196 | - [`SocketAddressError`] | |
197 | ||
198 | If the source address cannot be assigned to the socket. | |
199 | ||
200 | - [`ICMPSocketError`] | |
201 | ||
202 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
137 | 203 | |
138 | 204 | #### Example |
139 | ```python | |
205 | ||
206 | ```python | |
207 | >>> from icmplib import ping | |
208 | ||
140 | 209 | >>> host = ping('1.1.1.1', count=10, interval=0.2) |
141 | 210 | |
142 | >>> host.address # The IP address of the gateway or host | |
143 | '1.1.1.1' # that responded to the request | |
144 | ||
145 | >>> host.min_rtt # The minimum round-trip time | |
146 | 12.2 | |
147 | ||
148 | >>> host.avg_rtt # The average round-trip time | |
149 | 13.2 | |
150 | ||
151 | >>> host.max_rtt # The maximum round-trip time | |
152 | 17.6 | |
153 | ||
154 | >>> host.packets_sent # The number of packets transmitted to the | |
155 | 10 # destination host | |
156 | ||
157 | >>> host.packets_received # The number of packets sent by the remote | |
158 | 9 # host and received by the current host | |
159 | ||
160 | >>> host.packet_loss # Packet loss occurs when packets fail to | |
161 | 0.1 # reach their destination. Returns a float | |
162 | # between 0 and 1 (all packets are lost) | |
163 | >>> host.is_alive # Indicates whether the host is reachable | |
211 | >>> host.address # The IP address of the host that responded | |
212 | '1.1.1.1' # to the request | |
213 | ||
214 | >>> host.min_rtt # The minimum round-trip time in milliseconds | |
215 | 5.761 | |
216 | ||
217 | >>> host.avg_rtt # The average round-trip time in milliseconds | |
218 | 12.036 | |
219 | ||
220 | >>> host.max_rtt # The maximum round-trip time in milliseconds | |
221 | 16.207 | |
222 | ||
223 | >>> host.rtts # The list of round-trip times expressed in | |
224 | [ 11.595, 13.135, 9.614, # milliseconds | |
225 | 16.018, 11.960, 5.761, # The results are not rounded unlike other | |
226 | 16.207, 11.937, 12.098 ] # properties | |
227 | ||
228 | >>> host.packets_sent # The number of requests transmitted to the | |
229 | 10 # remote host | |
230 | ||
231 | >>> host.packets_received # The number of ICMP responses received from | |
232 | 9 # the remote host | |
233 | ||
234 | >>> host.packet_loss # Packet loss occurs when packets fail to | |
235 | 0.1 # reach their destination. Returns a float | |
236 | # between 0 and 1 (all packets are lost) | |
237 | ||
238 | >>> host.jitter # The jitter in milliseconds, defined as the | |
239 | 4.575 # variance of the latency of packets flowing | |
240 | # through the network | |
241 | ||
242 | >>> host.is_alive # Indicates whether the host is reachable | |
164 | 243 | True |
165 | 244 | ``` |
166 | 245 | |
167 | 246 | <br> |
168 | 247 | |
169 | ### Multiping | |
170 | Send *ICMP ECHO_REQUEST* packets to multiple network hosts. | |
171 | ||
172 | #### Definition | |
173 | ```python | |
174 | multiping(addresses, count=2, interval=1, timeout=2, id=PID, max_threads=10, **kwargs) | |
248 | ### multiping | |
249 | ||
250 | Send ICMP Echo Request packets to several network hosts. | |
251 | ||
252 | ```python | |
253 | multiping(addresses, count=2, interval=0.5, timeout=2, concurrent_tasks=50, source=None, family=None, privileged=True, **kwargs) | |
175 | 254 | ``` |
176 | 255 | |
177 | 256 | #### Parameters |
257 | ||
178 | 258 | - `addresses` |
179 | 259 | |
180 | The IP addresses of the gateways or hosts to which messages should be sent. | |
181 | ||
182 | - Type: `list of str` | |
260 | The IP addresses of the hosts to which messages should be sent. Hostnames and FQDNs are allowed but not recommended. You can easily retrieve their IP address by calling the built-in [`resolve`] function. | |
261 | ||
262 | - Type: `list[str]` | |
183 | 263 | |
184 | 264 | - `count` |
185 | 265 | |
193 | 273 | The interval in seconds between sending each packet. |
194 | 274 | |
195 | 275 | - Type: `int` or `float` |
196 | - Default: `1` | |
276 | - Default: `0.5` | |
197 | 277 | |
198 | 278 | - `timeout` |
199 | 279 | |
202 | 282 | - Type: `int` or `float` |
203 | 283 | - Default: `2` |
204 | 284 | |
205 | - `id` | |
206 | ||
207 | The identifier of the requests. This identifier will be incremented by one for each destination. | |
208 | ||
209 | - Type: `int` | |
210 | - Default: `PID` | |
211 | ||
212 | - `max_threads` | |
213 | ||
214 | The number of threads allowed to speed up processing. | |
215 | ||
216 | - Type: `int` | |
217 | - Default: `10` | |
218 | ||
219 | - `**kwargs` | |
220 | ||
221 | `Optional` Advanced use: arguments passed to `ICMPRequest` objects. | |
285 | - `concurrent_tasks` | |
286 | ||
287 | The maximum number of concurrent tasks to speed up processing. This value cannot exceed the maximum number of file descriptors configured on the operating system. | |
288 | ||
289 | - Type: `int` | |
290 | - Default: `50` | |
291 | ||
292 | - `source` | |
293 | ||
294 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destinations. This parameter should not be used if you are passing both IPv4 and IPv6 addresses to this function. | |
295 | ||
296 | - Type: `str` | |
297 | - Default: `None` | |
298 | ||
299 | - `family` | |
300 | ||
301 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
302 | ||
303 | - Type: `int` | |
304 | - Default: `None` | |
305 | ||
306 | - `privileged` | |
307 | ||
308 | When this option is enabled, this library fully manages the exchanges and the structure of ICMP packets. Disable this option if you want to use this function without root privileges and let the kernel handle ICMP headers. | |
309 | ||
310 | [Learn more about the `privileged` parameter.] | |
311 | ||
312 | *Only available on Unix systems. Ignored on Windows.* | |
313 | ||
314 | - Type: `bool` | |
315 | - Default: `True` | |
316 | ||
317 | - `payload` | |
318 | ||
319 | The payload content in bytes. A random payload is used by default. | |
320 | ||
321 | - Type: `bytes` | |
322 | - Default: `None` | |
323 | ||
324 | - `payload_size` | |
325 | ||
326 | The payload size. Ignored when the `payload` parameter is set. | |
327 | ||
328 | - Type: `int` | |
329 | - Default: `56` | |
330 | ||
331 | - `traffic_class` | |
332 | ||
333 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
334 | ||
335 | *Only available on Unix systems. Ignored on Windows.* | |
336 | ||
337 | - Type: `int` | |
338 | - Default: `0` | |
222 | 339 | |
223 | 340 | #### Return value |
224 | - `List of Host` | |
225 | ||
226 | A `list of Host` objects containing statistics about the desired destinations:<br> | |
227 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `packets_sent`,<br> | |
228 | `packets_received`, `packet_loss`, `is_alive`.<br> | |
341 | ||
342 | - A list of `Host` objects containing statistics about the desired destinations:<br> | |
343 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive` | |
344 | ||
229 | 345 | The list is sorted in the same order as the addresses passed in parameters. |
230 | 346 | |
231 | 347 | #### Exceptions |
232 | - `SocketPermissionError` | |
233 | ||
234 | If the permissions are insufficient to create a socket. | |
348 | ||
349 | - [`NameLookupError`] | |
350 | ||
351 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
352 | ||
353 | - [`SocketPermissionError`] | |
354 | ||
355 | If the privileges are insufficient to create the socket. | |
356 | ||
357 | - [`SocketAddressError`] | |
358 | ||
359 | If the source address cannot be assigned to the socket. | |
360 | ||
361 | - [`ICMPSocketError`] | |
362 | ||
363 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
235 | 364 | |
236 | 365 | #### Example |
237 | ```python | |
366 | ||
367 | ```python | |
368 | >>> from icmplib import multiping | |
369 | ||
238 | 370 | >>> hosts = multiping(['10.0.0.5', '127.0.0.1', '::1']) |
239 | 371 | |
240 | 372 | >>> for host in hosts: |
241 | 373 | ... if host.is_alive: |
242 | 374 | ... # See the Host class for details |
243 | ... print(f'{host.address} is alive!') | |
244 | ... | |
375 | ... print(f'{host.address} is up!') | |
245 | 376 | ... else: |
246 | ... print(f'{host.address} is dead!') | |
247 | ... | |
248 | ||
249 | # 10.0.0.5 is dead! | |
250 | # 127.0.0.1 is alive! | |
251 | # ::1 is alive! | |
252 | ``` | |
253 | ||
254 | <br> | |
255 | ||
256 | ### Traceroute | |
377 | ... print(f'{host.address} is down!') | |
378 | ||
379 | # 10.0.0.5 is down! | |
380 | # 127.0.0.1 is up! | |
381 | # ::1 is up! | |
382 | ``` | |
383 | ||
384 | <br> | |
385 | ||
386 | ### traceroute | |
387 | ||
257 | 388 | Determine the route to a destination host. |
258 | 389 | |
259 | The Internet is a large and complex aggregation of network hardware, connected together by gateways. Tracking the route one's packets follow can be difficult. This function utilizes the IP protocol time to live field and attempts to elicit an *ICMP TIME_EXCEEDED* response from each gateway along the path to some host. | |
260 | ||
261 | #### Definition | |
262 | ```python | |
263 | traceroute(address, count=3, interval=0.05, timeout=2, id=PID, traffic_class=0, max_hops=30, fast_mode=False, **kwargs) | |
390 | The Internet is a large and complex aggregation of network hardware, connected together by gateways. Tracking the route one's packets follow can be difficult. This function uses the IP protocol time to live field and attempts to elicit an ICMP Time Exceeded response from each gateway along the path to some host. | |
391 | ||
392 | *This function requires root privileges to run.* | |
393 | ||
394 | ```python | |
395 | traceroute(address, count=2, interval=0.05, timeout=2, first_hop=1, max_hops=30, fast=False, id=None, source=None, family=None, **kwargs) | |
264 | 396 | ``` |
265 | 397 | |
266 | 398 | #### Parameters |
399 | ||
267 | 400 | - `address` |
268 | 401 | |
269 | The destination IP address. | |
402 | The IP address, hostname or FQDN of the host to reach. For deterministic behavior, prefer to use an IP address. | |
270 | 403 | |
271 | 404 | - Type: `str` |
272 | 405 | |
275 | 408 | The number of ping to perform per hop. |
276 | 409 | |
277 | 410 | - Type: `int` |
278 | - Default: `3` | |
411 | - Default: `2` | |
279 | 412 | |
280 | 413 | - `interval` |
281 | 414 | |
291 | 424 | - Type: `int` or `float` |
292 | 425 | - Default: `2` |
293 | 426 | |
294 | - `id` | |
295 | ||
296 | The identifier of the request. Used to match the reply with the request.<br> | |
297 | In practice, a unique identifier is used for every ping process. | |
298 | ||
299 | - Type: `int` | |
300 | - Default: `PID` | |
301 | ||
302 | - `traffic_class` | |
303 | ||
304 | The traffic class of packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). | |
305 | ||
306 | Intermediate routers must be able to support this feature.<br> | |
307 | *Only available on Unix systems. Ignored on Windows.* | |
308 | ||
309 | - Type: `int` | |
310 | - Default: `0` | |
427 | - `first_hop` | |
428 | ||
429 | The initial time to live value used in outgoing probe packets. | |
430 | ||
431 | - Type: `int` | |
432 | - Default: `1` | |
311 | 433 | |
312 | 434 | - `max_hops` |
313 | 435 | |
316 | 438 | - Type: `int` |
317 | 439 | - Default: `30` |
318 | 440 | |
319 | - `fast_mode` | |
320 | ||
321 | When this option is enabled and an intermediate router has been reached, skip to the next hop rather than perform additional requests. The `count` parameter then becomes the maximum number of requests in case of no responses. | |
441 | - `fast` | |
442 | ||
443 | When this option is enabled and an intermediate router has been reached, skip to the next hop rather than perform additional requests. The `count` parameter then becomes the maximum number of requests in the event of no response. | |
322 | 444 | |
323 | 445 | - Type: `bool` |
324 | 446 | - Default: `False` |
325 | 447 | |
326 | - `**kwargs` | |
327 | ||
328 | `Optional` Advanced use: arguments passed to `ICMPRequest` objects. | |
448 | - `id` | |
449 | ||
450 | The identifier of ICMP requests. Used to match the responses with requests. In practice, a unique identifier should be used for every traceroute process. The library handles this identifier itself by default. | |
451 | ||
452 | - Type: `int` | |
453 | - Default: `None` | |
454 | ||
455 | - `source` | |
456 | ||
457 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destination. | |
458 | ||
459 | - Type: `str` | |
460 | - Default: `None` | |
461 | ||
462 | - `family` | |
463 | ||
464 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
465 | ||
466 | - Type: `int` | |
467 | - Default: `None` | |
468 | ||
469 | - `payload` | |
470 | ||
471 | The payload content in bytes. A random payload is used by default. | |
472 | ||
473 | - Type: `bytes` | |
474 | - Default: `None` | |
475 | ||
476 | - `payload_size` | |
477 | ||
478 | The payload size. Ignored when the `payload` parameter is set. | |
479 | ||
480 | - Type: `int` | |
481 | - Default: `56` | |
482 | ||
483 | - `traffic_class` | |
484 | ||
485 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
486 | ||
487 | *Only available on Unix systems. Ignored on Windows.* | |
488 | ||
489 | - Type: `int` | |
490 | - Default: `0` | |
329 | 491 | |
330 | 492 | #### Return value |
331 | - `List of Hop` | |
332 | ||
333 | A `list of Hop` objects representing the route to the desired host. A `Hop` is a `Host` object with an additional attribute: a `distance`. The list is sorted in ascending order according to the distance (in terms of hops) that separates the remote host from the current machine. | |
493 | ||
494 | - A list of `Hop` objects representing the route to the desired destination. A `Hop` has the same properties as a `Host` object but it also has a `distance`:<br> | |
495 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive`, `distance` | |
496 | ||
497 | The list is sorted in ascending order according to the distance, in terms of hops, that separates the remote host from the current machine. Gateways that do not respond to requests are not added to this list. | |
334 | 498 | |
335 | 499 | #### Exceptions |
336 | - `SocketPermissionError` | |
337 | ||
338 | If the permissions are insufficient to create a socket. | |
500 | ||
501 | - [`NameLookupError`] | |
502 | ||
503 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
504 | ||
505 | - [`SocketPermissionError`] | |
506 | ||
507 | If the privileges are insufficient to create the socket. | |
508 | ||
509 | - [`SocketAddressError`] | |
510 | ||
511 | If the source address cannot be assigned to the socket. | |
512 | ||
513 | - [`ICMPSocketError`] | |
514 | ||
515 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
339 | 516 | |
340 | 517 | #### Example |
341 | ```python | |
518 | ||
519 | ```python | |
520 | >>> from icmplib import traceroute | |
521 | ||
342 | 522 | >>> hops = traceroute('1.1.1.1') |
343 | 523 | |
344 | >>> print('Distance (ttl) Address Average round-trip time') | |
524 | >>> print('Distance/TTL Address Average round-trip time') | |
345 | 525 | >>> last_distance = 0 |
346 | 526 | |
347 | 527 | >>> for hop in hops: |
348 | 528 | ... if last_distance + 1 != hop.distance: |
349 | ... print('Some routers are not responding') | |
529 | ... print('Some gateways are not responding') | |
350 | 530 | ... |
351 | 531 | ... # See the Hop class for details |
352 | 532 | ... print(f'{hop.distance} {hop.address} {hop.avg_rtt} ms') |
353 | 533 | ... |
354 | 534 | ... last_distance = hop.distance |
535 | ||
536 | # Distance/TTL Address Average round-trip time | |
537 | # 1 10.0.0.1 5.196 ms | |
538 | # 2 194.149.169.49 7.552 ms | |
539 | # 3 194.149.166.54 12.21 ms | |
540 | # * Some gateways are not responding | |
541 | # 5 212.73.205.22 22.15 ms | |
542 | # 6 1.1.1.1 13.59 ms | |
543 | ``` | |
544 | ||
545 | <br> | |
546 | ||
547 | ### async_ping | |
548 | ||
549 | Send ICMP Echo Request packets to a network host. | |
550 | ||
551 | *This function is non-blocking.* | |
552 | ||
553 | ```python | |
554 | async_ping(address, count=4, interval=1, timeout=2, id=None, source=None, family=None, privileged=True, **kwargs) | |
555 | ``` | |
556 | ||
557 | #### Parameters, return value and exceptions | |
558 | ||
559 | The same parameters, return value and exceptions as for the [`ping`] function. | |
560 | ||
561 | #### Example | |
562 | ||
563 | ```python | |
564 | >>> import asyncio | |
565 | >>> from icmplib import async_ping | |
566 | ||
567 | >>> async def is_alive(address): | |
568 | ... host = await async_ping(address, count=10, interval=0.2) | |
569 | ... return host.is_alive | |
570 | ||
571 | >>> asyncio.run(is_alive('1.1.1.1')) | |
572 | True | |
573 | ``` | |
574 | ||
575 | <br> | |
576 | ||
577 | ### async_multiping | |
578 | ||
579 | Send ICMP Echo Request packets to several network hosts. | |
580 | ||
581 | *This function is non-blocking.* | |
582 | ||
583 | ```python | |
584 | async_multiping(addresses, count=2, interval=0.5, timeout=2, concurrent_tasks=50, source=None, family=None, privileged=True, **kwargs) | |
585 | ``` | |
586 | ||
587 | #### Parameters, return value and exceptions | |
588 | ||
589 | The same parameters, return values and exceptions as for the [`multiping`] function. | |
590 | ||
591 | #### Example | |
592 | ||
593 | ```python | |
594 | >>> import asyncio | |
595 | >>> from icmplib import async_multiping | |
596 | ||
597 | >>> async def are_alive(*addresses): | |
598 | ... hosts = await async_multiping(addresses) | |
599 | ... | |
600 | ... for host in hosts: | |
601 | ... if not host.is_alive: | |
602 | ... return False | |
355 | 603 | ... |
356 | ||
357 | # Distance (ttl) Address Average round-trip time | |
358 | # 1 10.0.0.1 5.196 ms | |
359 | # 2 194.149.169.49 7.552 ms | |
360 | # 3 194.149.166.54 12.21 ms | |
361 | # * Some routers are not responding | |
362 | # 5 212.73.205.22 22.15 ms | |
363 | # 6 1.1.1.1 13.59 ms | |
364 | ``` | |
365 | ||
366 | <br> | |
367 | ||
368 | ## ICMP sockets | |
369 | ||
370 | If you want to create your own functions and classes using the ICMP protocol, you can use the `ICMPv4Socket` (for IPv4) and the `ICMPv6Socket` (for IPv6 only). These classes have many methods and attributes in common. They manipulate `ICMPRequest` and `ICMPReply` objects. | |
371 | ||
372 | ``` | |
373 | ┌─────────────────┐ | |
374 | ┌─────────────────┐ send(...) │ ICMPSocket: │ receive() ┌─────────────────┐ | |
375 | │ ICMPRequest │ ────────────> │ ICMPv4Socket or │ ────────────> │ ICMPReply │ | |
376 | └─────────────────┘ │ ICMPv6Socket │ └─────────────────┘ | |
377 | └─────────────────┘ | |
378 | ``` | |
379 | ||
380 | ### ICMPRequest | |
381 | A user-created object that represents an *ICMP ECHO_REQUEST*. | |
382 | ||
383 | #### Definition | |
384 | ```python | |
385 | ICMPRequest(destination, id, sequence, payload=None, payload_size=56, timeout=2, ttl=64, traffic_class=0) | |
386 | ``` | |
387 | ||
388 | #### Parameters / Getters | |
389 | - `destination` | |
390 | ||
391 | The IP address of the gateway or host to which the message should be sent. | |
392 | ||
393 | - Type: `str` | |
394 | ||
395 | - `id` | |
396 | ||
397 | The identifier of the request. Used to match the reply with the request.<br> | |
398 | In practice, a unique identifier is used for every ping process. | |
399 | ||
400 | - Type: `int` | |
401 | ||
402 | - `sequence` | |
403 | ||
404 | The sequence number. Used to match the reply with the request.<br> | |
405 | Typically, the sequence number is incremented for each packet sent during the process. | |
406 | ||
407 | - Type: `int` | |
408 | ||
409 | - `payload` | |
410 | ||
411 | The payload content in bytes. A random payload is used by default. | |
412 | ||
413 | - Type: `bytes` | |
414 | - Default: `None` | |
415 | ||
416 | - `payload_size` | |
417 | ||
418 | The payload size. Ignored when the `payload` parameter is set. | |
419 | ||
420 | - Type: `int` | |
421 | - Default: `56` | |
422 | ||
423 | - `timeout` | |
424 | ||
425 | The maximum waiting time for receiving a reply in seconds. | |
426 | ||
427 | - Type: `int` or `float` | |
428 | - Default: `2` | |
429 | ||
430 | - `ttl` | |
431 | ||
432 | The time to live of the packet in seconds. | |
433 | ||
434 | - Type: `int` | |
435 | - Default: `64` | |
436 | ||
437 | - `traffic_class` | |
438 | ||
439 | The traffic class of the packet. Provides a defined level of service to the packet by setting the DS Field (formerly TOS) or the Traffic Class field of the IP header. Packets are delivered with the minimum priority by default (Best-effort delivery). | |
440 | ||
441 | Intermediate routers must be able to support this feature.<br> | |
442 | *Only available on Unix systems. Ignored on Windows.* | |
443 | ||
444 | - Type: `int` | |
445 | - Default: `0` | |
446 | ||
447 | #### Getters only | |
448 | - `time` | |
449 | ||
450 | The timestamp of the ICMP request. Initialized to zero when creating the request and replaced by `ICMPv4Socket` or `ICMPv6Socket` with the time of sending. | |
451 | ||
452 | - Type: `float` | |
453 | ||
454 | <br> | |
455 | ||
456 | ### ICMPReply | |
457 | A class that represents an ICMP reply. Generated from an `ICMPSocket` object (`ICMPv4Socket` or `ICMPv6Socket`). | |
458 | ||
459 | #### Definition | |
460 | ```python | |
461 | ICMPReply(source, id, sequence, type, code, bytes_received, time) | |
462 | ``` | |
463 | ||
464 | #### Parameters / Getters | |
465 | - `source` | |
466 | ||
467 | The IP address of the gateway or host that composes the ICMP message. | |
468 | ||
469 | - Type: `str` | |
470 | ||
471 | - `id` | |
472 | ||
473 | The identifier of the request. Used to match the reply with the request. | |
474 | ||
475 | - Type: `int` | |
476 | ||
477 | - `sequence` | |
478 | ||
479 | The sequence number. Used to match the reply with the request. | |
480 | ||
481 | - Type: `int` | |
482 | ||
483 | - `type` | |
484 | ||
485 | The type of message. | |
486 | ||
487 | - Type: `int` | |
488 | ||
489 | - `code` | |
490 | ||
491 | The error code. | |
492 | ||
493 | - Type: `int` | |
494 | ||
495 | - `bytes_received` | |
496 | ||
497 | The number of bytes received. | |
498 | ||
499 | - Type: `int` | |
500 | ||
501 | - `time` | |
502 | ||
503 | The timestamp of the ICMP reply. | |
504 | ||
505 | - Type: `float` | |
506 | ||
507 | #### Methods | |
508 | - `raise_for_status()` | |
509 | ||
510 | Throw an exception if the reply is not an *ICMP ECHO_REPLY*.<br> | |
511 | Otherwise, do nothing. | |
512 | ||
513 | - Raises `ICMPv4DestinationUnreachable`: If the ICMPv4 reply is type 3. | |
514 | - Raises `ICMPv4TimeExceeded`: If the ICMPv4 reply is type 11. | |
515 | - Raises `ICMPv6DestinationUnreachable`: If the ICMPv6 reply is type 1. | |
516 | - Raises `ICMPv6TimeExceeded`: If the ICMPv6 reply is type 3. | |
517 | - Raises `ICMPError`: If the reply is of another type and is not an *ICMP ECHO_REPLY*. | |
518 | ||
519 | <br> | |
520 | ||
521 | ### ICMPv4Socket | |
522 | Socket for sending and receiving ICMPv4 packets. | |
523 | ||
524 | #### Definition | |
525 | ```python | |
526 | ICMPv4Socket() | |
527 | ``` | |
528 | ||
529 | #### Methods | |
530 | - `__init__()` | |
531 | ||
532 | *Constructor. Automatically called: do not call it directly.* | |
533 | ||
534 | - Raises `SocketPermissionError`: If the permissions are insufficient to create the socket. | |
535 | ||
536 | - `__del__()` | |
537 | ||
538 | *Destructor. Automatically called: do not call it directly.* | |
539 | ||
540 | - Call the `close` method. | |
541 | ||
542 | - `send(request)` | |
543 | ||
544 | Send a request to a host. | |
545 | ||
546 | This operation is non-blocking. Use the `receive` method to get the reply. | |
547 | ||
548 | - Parameter `ICMPRequest`: The ICMP request you have created. | |
549 | - Raises `SocketBroadcastError`: If a broadcast address is used and the corresponding option is not enabled on the socket (ICMPv4 only). | |
550 | - Raises `SocketUnavailableError`: If the socket is closed. | |
551 | - Raises `ICMPSocketError`: If another error occurs while sending. | |
552 | ||
553 | - `receive()` | |
554 | ||
555 | Receive a reply from a host. | |
556 | ||
557 | This method can be called multiple times if you expect several responses (as with a broadcast address). | |
558 | ||
559 | - Raises `TimeoutExceeded`: If no response is received before the timeout defined in the request. This exception is also useful for stopping a possible loop in case of multiple responses. | |
560 | - Raises `SocketUnavailableError`: If the socket is closed. | |
561 | - Raises `ICMPSocketError`: If another error occurs while receiving. | |
562 | - Returns `ICMPReply`: An `ICMPReply` object containing the reply of the desired destination. See the `ICMPReply` class for details. | |
563 | ||
564 | - `close()` | |
565 | ||
566 | Close the socket. It cannot be used after this call. | |
567 | ||
568 | #### Getters only | |
569 | - `is_closed` | |
570 | ||
571 | Indicate whether the socket is closed. | |
572 | ||
573 | - Type: `bool` | |
574 | ||
575 | #### Getters / Setters | |
576 | - `broadcast` | |
577 | ||
578 | Enable or disable the broadcast support on the socket. | |
579 | ||
580 | - Type: `bool` | |
581 | - Default: `False` | |
582 | ||
583 | <br> | |
584 | ||
585 | ### ICMPv6Socket | |
586 | Socket for sending and receiving ICMPv6 packets. | |
587 | ||
588 | #### Definition | |
589 | ```python | |
590 | ICMPv6Socket() | |
591 | ``` | |
592 | ||
593 | #### Methods | |
594 | The same methods as for the `ICMPv4Socket` class. | |
595 | ||
596 | <br> | |
597 | ||
598 | ### Exceptions | |
599 | The library contains many exceptions to adapt to your needs: | |
600 | ||
601 | ``` | |
602 | ICMPLibError | |
603 | ├─ ICMPSocketError | |
604 | │ ├─ SocketPermissionError | |
605 | │ ├─ SocketUnavailableError | |
606 | │ ├─ SocketBroadcastError | |
607 | │ └─ TimeoutExceeded | |
608 | │ | |
609 | └─ ICMPError | |
610 | ├─ DestinationUnreachable | |
611 | │ ├─ ICMPv4DestinationUnreachable | |
612 | │ └─ ICMPv6DestinationUnreachable | |
613 | │ | |
614 | └─ TimeExceeded | |
615 | ├─ ICMPv4TimeExceeded | |
616 | └─ ICMPv6TimeExceeded | |
617 | ``` | |
618 | ||
619 | - `ICMPLibError`: Exception class for the icmplib package. | |
620 | - `ICMPSocketError`: Base class for ICMP sockets exceptions. | |
621 | - `SocketPermissionError`: Raised when the permissions are insufficient to create a socket. | |
622 | - `SocketUnavailableError`: Raised when an action is performed while the socket is closed. | |
623 | - `SocketBroadcastError`: Raised when a broadcast address is used and the corresponding option is not enabled on the socket. | |
624 | - `TimeoutExceeded`: Raised when a timeout occurs on a socket. | |
625 | - `ICMPError`: Base class for ICMP error messages. | |
626 | - `DestinationUnreachable`: Destination Unreachable message is generated by the host or its inbound gateway to inform the client that the destination is unreachable for some reason. | |
627 | - `TimeExceeded`: Time Exceeded message is generated by a gateway to inform the source of a discarded datagram due to the time to live field reaching zero. A Time Exceeded message may also be sent by a host if it fails to reassemble a fragmented datagram within its time limit. | |
628 | ||
629 | Use the `message` property to retrieve the error message. | |
630 | ||
631 | `ICMPError` subclasses have properties to retrieve the response (`reply` property) and the specific message of the error (`message` property). | |
632 | ||
633 | <br> | |
634 | ||
635 | ### Examples | |
636 | #### Sockets in action | |
637 | ```python | |
638 | def single_ping(address, timeout=2, id=PID): | |
639 | # Create an ICMP socket | |
640 | socket = ICMPv4Socket() | |
641 | ||
642 | # Create a request | |
643 | # See the ICMPRequest class for details | |
644 | request = ICMPRequest( | |
645 | destination=address, | |
646 | id=id, | |
647 | sequence=1, | |
648 | timeout=timeout) | |
649 | ||
650 | try: | |
651 | socket.send(request) | |
652 | ||
653 | # If the program arrives in this section, | |
654 | # it means that the packet has been transmitted | |
655 | ||
656 | reply = socket.receive() | |
657 | ||
658 | # If the program arrives in this section, | |
659 | # it means that a packet has been received | |
660 | # The reply has the same identifier and sequence number that | |
661 | # the request but it can come from an intermediate gateway | |
662 | ||
663 | reply.raise_for_status() | |
664 | ||
665 | # If the program arrives in this section, | |
666 | # it means that the destination host has responded to | |
667 | # the request | |
668 | ||
669 | except TimeoutExceeded as err: | |
670 | # The timeout has been reached | |
671 | # Equivalent to print(err.message) | |
672 | print(err) | |
673 | ||
674 | except DestinationUnreachable as err: | |
675 | # The reply indicates that the destination host is | |
676 | # unreachable | |
677 | print(err) | |
678 | ||
679 | # Retrieve the response | |
680 | reply = err.reply | |
681 | ||
682 | except TimeExceeded as err: | |
683 | # The reply indicates that the time to live exceeded | |
684 | # in transit | |
685 | print(err) | |
686 | ||
687 | # Retrieve the response | |
688 | reply = err.reply | |
689 | ||
690 | except ICMPLibError as err: | |
691 | # All other errors | |
692 | print(err) | |
693 | ||
694 | # Automatic socket closure (garbage collector) | |
695 | ``` | |
696 | ||
697 | #### Verbose ping | |
698 | ```python | |
699 | def verbose_ping(address, count=4, interval=1, timeout=2, id=PID): | |
700 | # ICMPRequest uses a payload of 56 bytes by default | |
701 | # You can modify it using the payload_size parameter | |
702 | print(f'PING {address}: 56 data bytes') | |
703 | ||
704 | # Detection of the socket to use | |
705 | if is_ipv6_address(address): | |
706 | socket = ICMPv6Socket() | |
707 | ||
708 | else: | |
709 | socket = ICMPv4Socket() | |
710 | ||
711 | for sequence in range(count): | |
712 | # We create an ICMP request | |
713 | request = ICMPRequest( | |
714 | destination=address, | |
715 | id=id, | |
716 | sequence=sequence, | |
717 | timeout=timeout) | |
718 | ||
719 | try: | |
720 | # We send the request | |
721 | socket.send(request) | |
722 | ||
723 | # We are awaiting receipt of an ICMP reply | |
724 | reply = socket.receive() | |
725 | ||
726 | # We received a reply | |
727 | # We display some information | |
728 | print(f'{reply.bytes_received} bytes from ' | |
729 | f'{reply.source}: ', end='') | |
730 | ||
731 | # We throw an exception if it is an ICMP error message | |
732 | reply.raise_for_status() | |
733 | ||
734 | # We calculate the round-trip time and we display it | |
735 | round_trip_time = (reply.time - request.time) * 1000 | |
736 | ||
737 | print(f'icmp_seq={sequence} ' | |
738 | f'time={round(round_trip_time, 3)} ms') | |
739 | ||
740 | # We pause before continuing | |
741 | if sequence < count - 1: | |
742 | sleep(interval) | |
743 | ||
744 | except TimeoutExceeded: | |
745 | # The timeout has been reached | |
746 | print(f'Request timeout for icmp_seq {sequence}') | |
747 | ||
748 | except ICMPError as err: | |
749 | # An ICMP error message has been received | |
750 | print(err) | |
751 | ||
752 | except ICMPLibError: | |
753 | # All other errors | |
754 | print('An error has occurred.') | |
755 | ||
756 | ||
757 | verbose_ping('1.1.1.1') | |
758 | ||
759 | # PING 1.1.1.1: 56 data bytes | |
760 | # 64 bytes from 1.1.1.1: icmp_seq=0 time=12.061 ms | |
761 | # 64 bytes from 1.1.1.1: icmp_seq=1 time=12.597 ms | |
762 | # 64 bytes from 1.1.1.1: icmp_seq=2 time=12.475 ms | |
763 | # 64 bytes from 1.1.1.1: icmp_seq=3 time=10.822 ms | |
764 | ``` | |
765 | ||
766 | <br> | |
767 | ||
768 | ## FAQ | |
769 | ||
770 | ### How to resolve a FQDN / domain name? | |
771 | Python has a method to do this in its libraries: | |
772 | ```python | |
773 | >>> import socket | |
774 | >>> socket.gethostbyname('github.com') | |
775 | '140.82.118.4' | |
776 | ``` | |
604 | ... return True | |
605 | ||
606 | >>> asyncio.run(are_alive('10.0.0.5', '127.0.0.1', '::1')) | |
607 | False | |
608 | ``` | |
609 | ||
610 | <br> | |
611 | ||
612 | ## Documentation | |
613 | ||
614 | This page only gives an overview of the features of icmplib. | |
615 | ||
616 | To learn more about the built-in functions, on how to create your own and handle exceptions, you can click on the following link: | |
617 | ||
618 | - :rocket: [Documentation] | |
777 | 619 | |
778 | 620 | ## Contributing |
779 | 621 | |
780 | 622 | Comments and enhancements are welcome. |
781 | 623 | |
782 | All development is done on [GitHub](https://github.com/ValentinBELYN/icmplib). Use [Issues](https://github.com/ValentinBELYN/icmplib/issues) to report problems and submit feature requests. Please include a minimal example that reproduces the bug. | |
624 | All development is done on [GitHub]. Use [Issues] to report problems and submit feature requests. Please include a minimal example that reproduces the bug. | |
783 | 625 | |
784 | 626 | ## Donate |
785 | 627 | |
786 | 628 | icmplib is completely free and open source. It has been fully developed on my free time. If you enjoy it, please consider donating to support the development. |
787 | 629 | |
788 | - [:tada: Donate via PayPal](https://paypal.me/ValentinBELYN) | |
630 | - :tada: [Donate via PayPal] | |
789 | 631 | |
790 | 632 | ## License |
791 | 633 | |
792 | Copyright 2017-2020 Valentin BELYN. | |
793 | ||
794 | Code released under the GNU LGPLv3 license. See the [LICENSE](LICENSE) for details. | |
634 | Copyright 2017-2021 Valentin BELYN. | |
635 | ||
636 | Code released under the GNU LGPLv3 license. See the [LICENSE] for details. | |
637 | ||
638 | [Learn more about the `privileged` parameter.]: docs/6-use-icmplib-without-privileges.md | |
639 | [Documentation]: docs | |
640 | [GitHub]: https://github.com/ValentinBELYN/icmplib | |
641 | [Issues]: https://github.com/ValentinBELYN/icmplib/issues | |
642 | [Donate via PayPal]: https://paypal.me/ValentinBELYN | |
643 | [LICENSE]: LICENSE | |
644 | [`ping`]: #ping | |
645 | [`multiping`]: #multiping | |
646 | [`ICMPv4Socket`]: docs/3-sockets.md#ICMPv4Socket | |
647 | [`ICMPv6Socket`]: docs/3-sockets.md#ICMPv6Socket | |
648 | [`NameLookupError`]: docs/4-exceptions.md#NameLookupError | |
649 | [`ICMPSocketError`]: docs/4-exceptions.md#ICMPSocketError | |
650 | [`SocketAddressError`]: docs/4-exceptions.md#SocketAddressError | |
651 | [`SocketPermissionError`]: docs/4-exceptions.md#SocketPermissionError | |
652 | [`resolve`]: docs/5-utilities.md#resolve |
0 | # Installation | |
1 | ||
2 | ## Requirements | |
3 | ||
4 | - Since icmplib 3, Python 3.7 or later is required to use the library. | |
5 | - If you are using Python 3.6 and you cannot update it, you can still install icmplib 2. | |
6 | - icmplib has been optimized to run on the most popular operating systems such as Linux, macOS and Windows. | |
7 | ||
8 | ## Installation and update | |
9 | ||
10 | - **Install** | |
11 | ||
12 | The recommended way to install icmplib is to use `pip3`: | |
13 | ||
14 | ```shell | |
15 | $ pip3 install icmplib | |
16 | ``` | |
17 | ||
18 | - **Update** | |
19 | ||
20 | Before updating icmplib, take care to read the release notes carefully to avoid any incompatibilities with your applications. | |
21 | ||
22 | ```shell | |
23 | $ pip3 install --upgrade icmplib | |
24 | ``` | |
25 | ||
26 | ## Imports | |
27 | ||
28 | Add the following instructions into your project to import icmplib (only import what you need): | |
29 | ||
30 | - **Basic functions** | |
31 | ||
32 | ```python | |
33 | from icmplib import ping, multiping, traceroute, resolve | |
34 | ``` | |
35 | ||
36 | - **Asynchronous functions** | |
37 | ||
38 | ```python | |
39 | from icmplib import async_ping, async_multiping, async_resolve | |
40 | ``` | |
41 | ||
42 | - **Sockets (advanced)** | |
43 | ||
44 | ```python | |
45 | from icmplib import ICMPv4Socket, ICMPv6Socket, AsyncSocket, ICMPRequest, ICMPReply | |
46 | ``` | |
47 | ||
48 | - **Exceptions** | |
49 | ||
50 | ```python | |
51 | from icmplib import ICMPLibError, NameLookupError, ICMPSocketError | |
52 | from icmplib import SocketAddressError, SocketPermissionError | |
53 | from icmplib import SocketUnavailableError, SocketBroadcastError, TimeoutExceeded | |
54 | from icmplib import ICMPError, DestinationUnreachable, TimeExceeded | |
55 | ``` |
0 | # Built-in functions | |
1 | ||
2 | ### ping | |
3 | ||
4 | Send ICMP Echo Request packets to a network host. | |
5 | ||
6 | ```python | |
7 | ping(address, count=4, interval=1, timeout=2, id=None, source=None, family=None, privileged=True, **kwargs) | |
8 | ``` | |
9 | ||
10 | #### Parameters | |
11 | ||
12 | - `address` | |
13 | ||
14 | The IP address, hostname or FQDN of the host to which messages should be sent. For deterministic behavior, prefer to use an IP address. | |
15 | ||
16 | - Type: `str` | |
17 | ||
18 | - `count` | |
19 | ||
20 | The number of ping to perform. | |
21 | ||
22 | - Type: `int` | |
23 | - Default: `4` | |
24 | ||
25 | - `interval` | |
26 | ||
27 | The interval in seconds between sending each packet. | |
28 | ||
29 | - Type: `int` or `float` | |
30 | - Default: `1` | |
31 | ||
32 | - `timeout` | |
33 | ||
34 | The maximum waiting time for receiving a reply in seconds. | |
35 | ||
36 | - Type: `int` or `float` | |
37 | - Default: `2` | |
38 | ||
39 | - `id` | |
40 | ||
41 | The identifier of ICMP requests. Used to match the responses with requests. In practice, a unique identifier should be used for every ping process. On Linux, this identifier is ignored when the `privileged` parameter is disabled. The library handles this identifier itself by default. | |
42 | ||
43 | - Type: `int` | |
44 | - Default: `None` | |
45 | ||
46 | - `source` | |
47 | ||
48 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destination. | |
49 | ||
50 | - Type: `str` | |
51 | - Default: `None` | |
52 | ||
53 | - `family` | |
54 | ||
55 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
56 | ||
57 | - Type: `int` | |
58 | - Default: `None` | |
59 | ||
60 | - `privileged` | |
61 | ||
62 | When this option is enabled, this library fully manages the exchanges and the structure of ICMP packets. Disable this option if you want to use this function without root privileges and let the kernel handle ICMP headers. | |
63 | ||
64 | [Learn more about the `privileged` parameter.] | |
65 | ||
66 | *Only available on Unix systems. Ignored on Windows.* | |
67 | ||
68 | - Type: `bool` | |
69 | - Default: `True` | |
70 | ||
71 | - `payload` | |
72 | ||
73 | The payload content in bytes. A random payload is used by default. | |
74 | ||
75 | - Type: `bytes` | |
76 | - Default: `None` | |
77 | ||
78 | - `payload_size` | |
79 | ||
80 | The payload size. Ignored when the `payload` parameter is set. | |
81 | ||
82 | - Type: `int` | |
83 | - Default: `56` | |
84 | ||
85 | - `traffic_class` | |
86 | ||
87 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
88 | ||
89 | *Only available on Unix systems. Ignored on Windows.* | |
90 | ||
91 | - Type: `int` | |
92 | - Default: `0` | |
93 | ||
94 | #### Return value | |
95 | ||
96 | - A `Host` object containing statistics about the desired destination:<br> | |
97 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive` | |
98 | ||
99 | #### Exceptions | |
100 | ||
101 | - [`NameLookupError`] | |
102 | ||
103 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
104 | ||
105 | - [`SocketPermissionError`] | |
106 | ||
107 | If the privileges are insufficient to create the socket. | |
108 | ||
109 | - [`SocketAddressError`] | |
110 | ||
111 | If the source address cannot be assigned to the socket. | |
112 | ||
113 | - [`ICMPSocketError`] | |
114 | ||
115 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
116 | ||
117 | #### Example | |
118 | ||
119 | ```python | |
120 | >>> from icmplib import ping | |
121 | ||
122 | >>> host = ping('1.1.1.1', count=10, interval=0.2) | |
123 | ||
124 | >>> host.address # The IP address of the host that responded | |
125 | '1.1.1.1' # to the request | |
126 | ||
127 | >>> host.min_rtt # The minimum round-trip time in milliseconds | |
128 | 5.761 | |
129 | ||
130 | >>> host.avg_rtt # The average round-trip time in milliseconds | |
131 | 12.036 | |
132 | ||
133 | >>> host.max_rtt # The maximum round-trip time in milliseconds | |
134 | 16.207 | |
135 | ||
136 | >>> host.rtts # The list of round-trip times expressed in | |
137 | [ 11.595, 13.135, 9.614, # milliseconds | |
138 | 16.018, 11.960, 5.761, # The results are not rounded unlike other | |
139 | 16.207, 11.937, 12.098 ] # properties | |
140 | ||
141 | >>> host.packets_sent # The number of requests transmitted to the | |
142 | 10 # remote host | |
143 | ||
144 | >>> host.packets_received # The number of ICMP responses received from | |
145 | 9 # the remote host | |
146 | ||
147 | >>> host.packet_loss # Packet loss occurs when packets fail to | |
148 | 0.1 # reach their destination. Returns a float | |
149 | # between 0 and 1 (all packets are lost) | |
150 | ||
151 | >>> host.jitter # The jitter in milliseconds, defined as the | |
152 | 4.575 # variance of the latency of packets flowing | |
153 | # through the network | |
154 | ||
155 | >>> host.is_alive # Indicates whether the host is reachable | |
156 | True | |
157 | ``` | |
158 | ||
159 | <br> | |
160 | ||
161 | ### multiping | |
162 | ||
163 | Send ICMP Echo Request packets to several network hosts. | |
164 | ||
165 | ```python | |
166 | multiping(addresses, count=2, interval=0.5, timeout=2, concurrent_tasks=50, source=None, family=None, privileged=True, **kwargs) | |
167 | ``` | |
168 | ||
169 | #### Parameters | |
170 | ||
171 | - `addresses` | |
172 | ||
173 | The IP addresses of the hosts to which messages should be sent. Hostnames and FQDNs are allowed but not recommended. You can easily retrieve their IP address by calling the built-in [`resolve`] function. | |
174 | ||
175 | - Type: `list[str]` | |
176 | ||
177 | - `count` | |
178 | ||
179 | The number of ping to perform per address. | |
180 | ||
181 | - Type: `int` | |
182 | - Default: `2` | |
183 | ||
184 | - `interval` | |
185 | ||
186 | The interval in seconds between sending each packet. | |
187 | ||
188 | - Type: `int` or `float` | |
189 | - Default: `0.5` | |
190 | ||
191 | - `timeout` | |
192 | ||
193 | The maximum waiting time for receiving a reply in seconds. | |
194 | ||
195 | - Type: `int` or `float` | |
196 | - Default: `2` | |
197 | ||
198 | - `concurrent_tasks` | |
199 | ||
200 | The maximum number of concurrent tasks to speed up processing. This value cannot exceed the maximum number of file descriptors configured on the operating system. | |
201 | ||
202 | - Type: `int` | |
203 | - Default: `50` | |
204 | ||
205 | - `source` | |
206 | ||
207 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destinations. This parameter should not be used if you are passing both IPv4 and IPv6 addresses to this function. | |
208 | ||
209 | - Type: `str` | |
210 | - Default: `None` | |
211 | ||
212 | - `family` | |
213 | ||
214 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
215 | ||
216 | - Type: `int` | |
217 | - Default: `None` | |
218 | ||
219 | - `privileged` | |
220 | ||
221 | When this option is enabled, this library fully manages the exchanges and the structure of ICMP packets. Disable this option if you want to use this function without root privileges and let the kernel handle ICMP headers. | |
222 | ||
223 | [Learn more about the `privileged` parameter.] | |
224 | ||
225 | *Only available on Unix systems. Ignored on Windows.* | |
226 | ||
227 | - Type: `bool` | |
228 | - Default: `True` | |
229 | ||
230 | - `payload` | |
231 | ||
232 | The payload content in bytes. A random payload is used by default. | |
233 | ||
234 | - Type: `bytes` | |
235 | - Default: `None` | |
236 | ||
237 | - `payload_size` | |
238 | ||
239 | The payload size. Ignored when the `payload` parameter is set. | |
240 | ||
241 | - Type: `int` | |
242 | - Default: `56` | |
243 | ||
244 | - `traffic_class` | |
245 | ||
246 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
247 | ||
248 | *Only available on Unix systems. Ignored on Windows.* | |
249 | ||
250 | - Type: `int` | |
251 | - Default: `0` | |
252 | ||
253 | #### Return value | |
254 | ||
255 | - A list of `Host` objects containing statistics about the desired destinations:<br> | |
256 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive` | |
257 | ||
258 | The list is sorted in the same order as the addresses passed in parameters. | |
259 | ||
260 | #### Exceptions | |
261 | ||
262 | - [`NameLookupError`] | |
263 | ||
264 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
265 | ||
266 | - [`SocketPermissionError`] | |
267 | ||
268 | If the privileges are insufficient to create the socket. | |
269 | ||
270 | - [`SocketAddressError`] | |
271 | ||
272 | If the source address cannot be assigned to the socket. | |
273 | ||
274 | - [`ICMPSocketError`] | |
275 | ||
276 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
277 | ||
278 | #### Example | |
279 | ||
280 | ```python | |
281 | >>> from icmplib import multiping | |
282 | ||
283 | >>> hosts = multiping(['10.0.0.5', '127.0.0.1', '::1']) | |
284 | ||
285 | >>> for host in hosts: | |
286 | ... if host.is_alive: | |
287 | ... # See the Host class for details | |
288 | ... print(f'{host.address} is up!') | |
289 | ... else: | |
290 | ... print(f'{host.address} is down!') | |
291 | ||
292 | # 10.0.0.5 is down! | |
293 | # 127.0.0.1 is up! | |
294 | # ::1 is up! | |
295 | ``` | |
296 | ||
297 | <br> | |
298 | ||
299 | ### traceroute | |
300 | ||
301 | Determine the route to a destination host. | |
302 | ||
303 | The Internet is a large and complex aggregation of network hardware, connected together by gateways. Tracking the route one's packets follow can be difficult. This function uses the IP protocol time to live field and attempts to elicit an ICMP Time Exceeded response from each gateway along the path to some host. | |
304 | ||
305 | *This function requires root privileges to run.* | |
306 | ||
307 | ```python | |
308 | traceroute(address, count=2, interval=0.05, timeout=2, first_hop=1, max_hops=30, fast=False, id=None, source=None, family=None, **kwargs) | |
309 | ``` | |
310 | ||
311 | #### Parameters | |
312 | ||
313 | - `address` | |
314 | ||
315 | The IP address, hostname or FQDN of the host to reach. For deterministic behavior, prefer to use an IP address. | |
316 | ||
317 | - Type: `str` | |
318 | ||
319 | - `count` | |
320 | ||
321 | The number of ping to perform per hop. | |
322 | ||
323 | - Type: `int` | |
324 | - Default: `2` | |
325 | ||
326 | - `interval` | |
327 | ||
328 | The interval in seconds between sending each packet. | |
329 | ||
330 | - Type: `int` or `float` | |
331 | - Default: `0.05` | |
332 | ||
333 | - `timeout` | |
334 | ||
335 | The maximum waiting time for receiving a reply in seconds. | |
336 | ||
337 | - Type: `int` or `float` | |
338 | - Default: `2` | |
339 | ||
340 | - `first_hop` | |
341 | ||
342 | The initial time to live value used in outgoing probe packets. | |
343 | ||
344 | - Type: `int` | |
345 | - Default: `1` | |
346 | ||
347 | - `max_hops` | |
348 | ||
349 | The maximum time to live (max number of hops) used in outgoing probe packets. | |
350 | ||
351 | - Type: `int` | |
352 | - Default: `30` | |
353 | ||
354 | - `fast` | |
355 | ||
356 | When this option is enabled and an intermediate router has been reached, skip to the next hop rather than perform additional requests. The `count` parameter then becomes the maximum number of requests in the event of no response. | |
357 | ||
358 | - Type: `bool` | |
359 | - Default: `False` | |
360 | ||
361 | - `id` | |
362 | ||
363 | The identifier of ICMP requests. Used to match the responses with requests. In practice, a unique identifier should be used for every traceroute process. The library handles this identifier itself by default. | |
364 | ||
365 | - Type: `int` | |
366 | - Default: `None` | |
367 | ||
368 | - `source` | |
369 | ||
370 | The IP address from which you want to send packets. By default, the interface is automatically chosen according to the specified destination. | |
371 | ||
372 | - Type: `str` | |
373 | - Default: `None` | |
374 | ||
375 | - `family` | |
376 | ||
377 | The address family if a hostname or FQDN is specified. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first before searching for IPv6 addresses. | |
378 | ||
379 | - Type: `int` | |
380 | - Default: `None` | |
381 | ||
382 | - `payload` | |
383 | ||
384 | The payload content in bytes. A random payload is used by default. | |
385 | ||
386 | - Type: `bytes` | |
387 | - Default: `None` | |
388 | ||
389 | - `payload_size` | |
390 | ||
391 | The payload size. Ignored when the `payload` parameter is set. | |
392 | ||
393 | - Type: `int` | |
394 | - Default: `56` | |
395 | ||
396 | - `traffic_class` | |
397 | ||
398 | The traffic class of ICMP packets. Provides a defined level of service to packets by setting the DS Field (formerly TOS) or the Traffic Class field of IP headers. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
399 | ||
400 | *Only available on Unix systems. Ignored on Windows.* | |
401 | ||
402 | - Type: `int` | |
403 | - Default: `0` | |
404 | ||
405 | #### Return value | |
406 | ||
407 | - A list of `Hop` objects representing the route to the desired destination. A `Hop` has the same properties as a `Host` object but it also has a `distance`:<br> | |
408 | `address`, `min_rtt`, `avg_rtt`, `max_rtt`, `rtts`, `packets_sent`, `packets_received`, `packet_loss`, `jitter`, `is_alive`, `distance` | |
409 | ||
410 | The list is sorted in ascending order according to the distance, in terms of hops, that separates the remote host from the current machine. Gateways that do not respond to requests are not added to this list. | |
411 | ||
412 | #### Exceptions | |
413 | ||
414 | - [`NameLookupError`] | |
415 | ||
416 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
417 | ||
418 | - [`SocketPermissionError`] | |
419 | ||
420 | If the privileges are insufficient to create the socket. | |
421 | ||
422 | - [`SocketAddressError`] | |
423 | ||
424 | If the source address cannot be assigned to the socket. | |
425 | ||
426 | - [`ICMPSocketError`] | |
427 | ||
428 | If another error occurs. See the [`ICMPv4Socket`] or [`ICMPv6Socket`] class for details. | |
429 | ||
430 | #### Example | |
431 | ||
432 | ```python | |
433 | >>> from icmplib import traceroute | |
434 | ||
435 | >>> hops = traceroute('1.1.1.1') | |
436 | ||
437 | >>> print('Distance/TTL Address Average round-trip time') | |
438 | >>> last_distance = 0 | |
439 | ||
440 | >>> for hop in hops: | |
441 | ... if last_distance + 1 != hop.distance: | |
442 | ... print('Some gateways are not responding') | |
443 | ... | |
444 | ... # See the Hop class for details | |
445 | ... print(f'{hop.distance} {hop.address} {hop.avg_rtt} ms') | |
446 | ... | |
447 | ... last_distance = hop.distance | |
448 | ||
449 | # Distance/TTL Address Average round-trip time | |
450 | # 1 10.0.0.1 5.196 ms | |
451 | # 2 194.149.169.49 7.552 ms | |
452 | # 3 194.149.166.54 12.21 ms | |
453 | # * Some gateways are not responding | |
454 | # 5 212.73.205.22 22.15 ms | |
455 | # 6 1.1.1.1 13.59 ms | |
456 | ``` | |
457 | ||
458 | <br> | |
459 | ||
460 | ### async_ping | |
461 | ||
462 | Send ICMP Echo Request packets to a network host. | |
463 | ||
464 | *This function is non-blocking.* | |
465 | ||
466 | ```python | |
467 | async_ping(address, count=4, interval=1, timeout=2, id=None, source=None, family=None, privileged=True, **kwargs) | |
468 | ``` | |
469 | ||
470 | #### Parameters, return value and exceptions | |
471 | ||
472 | The same parameters, return value and exceptions as for the [`ping`] function. | |
473 | ||
474 | #### Example | |
475 | ||
476 | ```python | |
477 | >>> import asyncio | |
478 | >>> from icmplib import async_ping | |
479 | ||
480 | >>> async def is_alive(address): | |
481 | ... host = await async_ping(address, count=10, interval=0.2) | |
482 | ... return host.is_alive | |
483 | ||
484 | >>> asyncio.run(is_alive('1.1.1.1')) | |
485 | True | |
486 | ``` | |
487 | ||
488 | <br> | |
489 | ||
490 | ### async_multiping | |
491 | ||
492 | Send ICMP Echo Request packets to several network hosts. | |
493 | ||
494 | *This function is non-blocking.* | |
495 | ||
496 | ```python | |
497 | async_multiping(addresses, count=2, interval=0.5, timeout=2, concurrent_tasks=50, source=None, family=None, privileged=True, **kwargs) | |
498 | ``` | |
499 | ||
500 | #### Parameters, return value and exceptions | |
501 | ||
502 | The same parameters, return values and exceptions as for the [`multiping`] function. | |
503 | ||
504 | #### Example | |
505 | ||
506 | ```python | |
507 | >>> import asyncio | |
508 | >>> from icmplib import async_multiping | |
509 | ||
510 | >>> async def are_alive(*addresses): | |
511 | ... hosts = await async_multiping(addresses) | |
512 | ... | |
513 | ... for host in hosts: | |
514 | ... if not host.is_alive: | |
515 | ... return False | |
516 | ... | |
517 | ... return True | |
518 | ||
519 | >>> asyncio.run(are_alive('10.0.0.5', '127.0.0.1', '::1')) | |
520 | False | |
521 | ``` | |
522 | ||
523 | [`ping`]: #ping | |
524 | [`multiping`]: #multiping | |
525 | [`ICMPv4Socket`]: 3-sockets.md#ICMPv4Socket | |
526 | [`ICMPv6Socket`]: 3-sockets.md#ICMPv6Socket | |
527 | [`NameLookupError`]: 4-exceptions.md#NameLookupError | |
528 | [`ICMPSocketError`]: 4-exceptions.md#ICMPSocketError | |
529 | [`SocketAddressError`]: 4-exceptions.md#SocketAddressError | |
530 | [`SocketPermissionError`]: 4-exceptions.md#SocketPermissionError | |
531 | [`resolve`]: 5-utilities.md#resolve | |
532 | [Learn more about the `privileged` parameter.]: 6-use-icmplib-without-privileges.md |
0 | # Sockets and classes | |
1 | ||
2 | If you want to create your own functions and classes using the ICMP protocol, you can use the [`ICMPv4Socket`] (for IPv4 only) and the [`ICMPv6Socket`] (for IPv6 only). These classes have many methods and properties in common. They manipulate [`ICMPRequest`] and [`ICMPReply`] objects. | |
3 | ||
4 | ``` | |
5 | ┌──────────────────┐ | |
6 | ┌─────────────────┐ send(...) │ ICMPv4Socket │ receive() ┌─────────────────┐ | |
7 | │ ICMPRequest │ ────────────> │ or │ ────────────> │ ICMPReply │ | |
8 | └─────────────────┘ │ ICMPv6Socket │ └─────────────────┘ | |
9 | └──────────────────┘ | |
10 | ``` | |
11 | ||
12 | ### ICMPRequest | |
13 | ||
14 | A user-created object that represents an ICMP Echo Request. | |
15 | ||
16 | ```python | |
17 | ICMPRequest(destination, id, sequence, payload=None, payload_size=56, ttl=64, traffic_class=0) | |
18 | ``` | |
19 | ||
20 | #### Parameters and properties | |
21 | ||
22 | - `destination` | |
23 | ||
24 | The IP address of the host to which the message should be sent. | |
25 | ||
26 | - Type: `str` | |
27 | ||
28 | - `id` | |
29 | ||
30 | The identifier of the request. Used to match the reply with the request. In practice, a unique identifier is used for every ping process. On Linux, this identifier is automatically replaced if the request is sent from an unprivileged socket. | |
31 | ||
32 | - Type: `int` | |
33 | ||
34 | - `sequence` | |
35 | ||
36 | The sequence number. Used to match the reply with the request. Typically, the sequence number is incremented for each packet sent during the process. | |
37 | ||
38 | - Type: `int` | |
39 | ||
40 | - `payload` | |
41 | ||
42 | The payload content in bytes. A random payload is used by default. | |
43 | ||
44 | - Type: `bytes` | |
45 | - Default: `None` | |
46 | ||
47 | - `payload_size` | |
48 | ||
49 | The payload size. Ignored when the `payload` parameter is set. | |
50 | ||
51 | - Type: `int` | |
52 | - Default: `56` | |
53 | ||
54 | - `ttl` | |
55 | ||
56 | The time to live of the packet in terms of hops. | |
57 | ||
58 | - Type: `int` | |
59 | - Default: `64` | |
60 | ||
61 | - `traffic_class` | |
62 | ||
63 | The traffic class of the ICMP packet. Provides a defined level of service to the packet by setting the DS Field (formerly TOS) or the Traffic Class field of the IP header. Packets are delivered with the minimum priority by default (Best-effort delivery). Intermediate routers must be able to support this feature. | |
64 | ||
65 | *Only available on Unix systems. Ignored on Windows.* | |
66 | ||
67 | - Type: `int` | |
68 | - Default: `0` | |
69 | ||
70 | #### Properties only | |
71 | ||
72 | - `time` | |
73 | ||
74 | The timestamp of the ICMP request. Initialized to zero when creating the request and replaced by the `send` method of an ICMP socket with the time of sending. | |
75 | ||
76 | - Type: `float` | |
77 | ||
78 | <br> | |
79 | ||
80 | ### ICMPReply | |
81 | ||
82 | A class that represents an ICMP reply. Generated from an ICMP socket. | |
83 | ||
84 | ```python | |
85 | ICMPReply(source, family, id, sequence, type, code, bytes_received, time) | |
86 | ``` | |
87 | ||
88 | #### Parameters and properties | |
89 | ||
90 | - `source` | |
91 | ||
92 | The IP address of the host that composes the ICMP message. | |
93 | ||
94 | - Type: `str` | |
95 | ||
96 | - `family` | |
97 | ||
98 | The address family. Can be set to `4` for IPv4 or `6` for IPv6 addresses. | |
99 | ||
100 | - Type: `int` | |
101 | ||
102 | - `id` | |
103 | ||
104 | The identifier of the reply. Used to match the reply with the request. | |
105 | ||
106 | - Type: `int` | |
107 | ||
108 | - `sequence` | |
109 | ||
110 | The sequence number. Used to match the reply with the request. | |
111 | ||
112 | - Type: `int` | |
113 | ||
114 | - `type` | |
115 | ||
116 | The type of ICMP message. | |
117 | ||
118 | - Type: `int` | |
119 | ||
120 | - `code` | |
121 | ||
122 | The ICMP error code. | |
123 | ||
124 | - Type: `int` | |
125 | ||
126 | - `bytes_received` | |
127 | ||
128 | The number of bytes received. | |
129 | ||
130 | - Type: `int` | |
131 | ||
132 | - `time` | |
133 | ||
134 | The timestamp of the ICMP reply. | |
135 | ||
136 | - Type: `float` | |
137 | ||
138 | #### Methods | |
139 | ||
140 | - `raise_for_status()` | |
141 | ||
142 | Throw an exception if the reply is not an ICMP Echo Reply. Otherwise, do nothing. | |
143 | ||
144 | - Raises [`DestinationUnreachable`]: If the destination is unreachable for some reason. | |
145 | - Raises [`TimeExceeded`]: If the time to live field of the ICMP request has reached zero. | |
146 | - Raises [`ICMPError`]: Raised for any other type and ICMP error code, except ICMP Echo Reply messages. | |
147 | ||
148 | <br> | |
149 | ||
150 | ### ICMPv4Socket | |
151 | ||
152 | Class for sending and receiving ICMPv4 packets. | |
153 | ||
154 | ```python | |
155 | ICMPv4Socket(address=None, privileged=True) | |
156 | ``` | |
157 | ||
158 | #### Parameters | |
159 | ||
160 | - `source` | |
161 | ||
162 | The IP address from which you want to listen and send packets. By default, the socket listens on all interfaces. | |
163 | ||
164 | - Type: `str` | |
165 | - Default: `None` | |
166 | ||
167 | - `privileged` | |
168 | ||
169 | When this option is enabled, the socket fully manages the exchanges and the structure of the ICMP packets. Disable this option if you want to instantiate and use the socket without root privileges and let the kernel handle ICMP headers. | |
170 | ||
171 | *Only available on Unix systems. Ignored on Windows.* | |
172 | ||
173 | - Type: `bool` | |
174 | - Default: `True` | |
175 | ||
176 | #### Methods | |
177 | ||
178 | - `__init__(address=None, privileged=True)` | |
179 | ||
180 | *Constructor. Automatically called: do not call it directly.* | |
181 | ||
182 | - Raises [`SocketPermissionError`]: If the privileges are insufficient to create the socket. | |
183 | - Raises [`SocketAddressError`]: If the requested address cannot be assigned to the socket. | |
184 | - Raises [`ICMPSocketError`]: If another error occurs while creating the socket. | |
185 | ||
186 | - `__enter__()` | |
187 | ||
188 | Return this object. | |
189 | ||
190 | - `__exit__(type, value, traceback)` | |
191 | ||
192 | Call the `close` method. | |
193 | ||
194 | - `__del__()` | |
195 | ||
196 | *Destructor. Automatically called: do not call it directly.* | |
197 | ||
198 | Call the `close` method. | |
199 | ||
200 | - `send(request)` | |
201 | ||
202 | Send an ICMP request message over the network to a remote host. | |
203 | ||
204 | This operation is non-blocking. Use the `receive` method to get the reply. | |
205 | ||
206 | - Parameter `request` ([`ICMPRequest`]): The ICMP request you have created. If the socket is used in non-privileged mode on a Linux system, the identifier defined in the request will be replaced by the kernel. | |
207 | - Raises [`SocketBroadcastError`]: If a broadcast address is used and the corresponding option is not enabled on the socket (ICMPv4 only). | |
208 | - Raises [`SocketUnavailableError`]: If the socket is closed. | |
209 | - Raises [`ICMPSocketError`]: If another error occurs while sending. | |
210 | ||
211 | - `receive(request=None, timeout=2)` | |
212 | ||
213 | Receive an ICMP reply message from the socket. | |
214 | ||
215 | This method can be called multiple times if you expect several responses as with a broadcast address. | |
216 | ||
217 | - Parameter `request` ([`ICMPRequest`]): The ICMP request to use to match the response. By default, all ICMP packets arriving on the socket are returned. | |
218 | - Parameter `timeout` (`int` or `float`): The maximum waiting time for receiving the response in seconds. Default to `2`. | |
219 | - Raises [`TimeoutExceeded`]: If no response is received before the timeout specified in parameters. | |
220 | - Raises [`SocketUnavailableError`]: If the socket is closed. | |
221 | - Raises [`ICMPSocketError`]: If another error occurs while receiving. | |
222 | ||
223 | Returns an [`ICMPReply`] object representing the response of the desired destination or an upstream gateway. | |
224 | ||
225 | - `close()` | |
226 | ||
227 | Close the socket. It cannot be used after this call. | |
228 | ||
229 | #### Properties only | |
230 | ||
231 | - `address` | |
232 | ||
233 | The IP address from which the socket listens and sends packets. Return `None` if the socket listens on all interfaces. | |
234 | ||
235 | - Type: `str` | |
236 | ||
237 | - `is_privileged` | |
238 | ||
239 | Indicate whether the socket is running in privileged mode. | |
240 | ||
241 | - Type: `bool` | |
242 | ||
243 | - `is_closed` | |
244 | ||
245 | Indicate whether the socket is closed. | |
246 | ||
247 | - Type: `bool` | |
248 | ||
249 | #### Properties and setters | |
250 | ||
251 | - `blocking` | |
252 | ||
253 | Set the blocking or non-blocking mode of the socket. | |
254 | ||
255 | - Type: `bool` | |
256 | - Default: `False` | |
257 | ||
258 | - `broadcast` | |
259 | ||
260 | Enable or disable the broadcast support on the socket. | |
261 | ||
262 | - Type: `bool` | |
263 | - Default: `False` | |
264 | ||
265 | <br> | |
266 | ||
267 | ### ICMPv6Socket | |
268 | ||
269 | Class for sending and receiving ICMPv6 packets. | |
270 | ||
271 | ```python | |
272 | ICMPv6Socket(address=None, privileged=True) | |
273 | ``` | |
274 | ||
275 | #### Methods and properties | |
276 | ||
277 | The same methods and properties as for the [`ICMPv4Socket`] class, except the `broadcast` property. | |
278 | ||
279 | <br> | |
280 | ||
281 | ### AsyncSocket | |
282 | ||
283 | A wrapper for ICMP sockets which makes them asynchronous. | |
284 | ||
285 | ```python | |
286 | AsyncSocket(icmp_sock) | |
287 | ``` | |
288 | ||
289 | #### Parameters | |
290 | ||
291 | - `icmp_sock` | |
292 | ||
293 | An ICMP socket. Once the wrapper is instantiated, this socket should no longer be used directly. | |
294 | ||
295 | - Type: [`ICMPv4Socket`] or [`ICMPv6Socket`] | |
296 | ||
297 | #### Methods | |
298 | ||
299 | The same methods as for the underlying ICMP socket, except: | |
300 | ||
301 | - `receive(request=None, timeout=2)` | |
302 | ||
303 | This function is now non-blocking. It must be awaited. | |
304 | ||
305 | - `detach()` | |
306 | ||
307 | Detach the socket from the wrapper and return it. The wrapper cannot be used after this call but the socket can be reused for other purposes. | |
308 | ||
309 | - `close()` | |
310 | ||
311 | Detach the underlying socket from the wrapper and close it. Both cannot be used after this call. | |
312 | ||
313 | #### Properties | |
314 | ||
315 | The same properties as for the underlying ICMP socket, except: | |
316 | ||
317 | - `is_closed` | |
318 | ||
319 | Indicate whether the underlying socket is closed or detached from this wrapper. | |
320 | ||
321 | - Type: `bool` | |
322 | ||
323 | <br> | |
324 | ||
325 | ### Examples | |
326 | ||
327 | #### Sockets in action | |
328 | ||
329 | ```python | |
330 | from icmplib import * | |
331 | ||
332 | ||
333 | def one_ping(address, timeout=2, id=PID): | |
334 | # Create an ICMP socket | |
335 | sock = ICMPv4Socket() | |
336 | ||
337 | # Create an ICMP request | |
338 | # See the 'ICMPRequest' class for details | |
339 | request = ICMPRequest( | |
340 | destination=address, | |
341 | id=id, | |
342 | sequence=1) | |
343 | ||
344 | try: | |
345 | sock.send(request) | |
346 | ||
347 | # If the program arrives in this section, it means that the | |
348 | # packet has been transmitted. | |
349 | ||
350 | reply = sock.receive(request, timeout) | |
351 | ||
352 | # If the program arrives in this section, it means that a | |
353 | # packet has been received. The reply has the same identifier | |
354 | # and sequence number that the request but it can come from | |
355 | # an intermediate gateway. | |
356 | ||
357 | reply.raise_for_status() | |
358 | ||
359 | # If the program arrives in this section, it means that the | |
360 | # destination host has responded to the request. | |
361 | ||
362 | except TimeoutExceeded as err: | |
363 | # The timeout has been reached | |
364 | print(err) | |
365 | ||
366 | except DestinationUnreachable as err: | |
367 | # The reply indicates that the destination host is unreachable | |
368 | print(err) | |
369 | ||
370 | except TimeExceeded as err: | |
371 | # The reply indicates that the time to live exceeded in transit | |
372 | print(err) | |
373 | ||
374 | except ICMPLibError as err: | |
375 | # All other errors | |
376 | print(err) | |
377 | ||
378 | # Automatic socket closure (garbage collector) | |
379 | ``` | |
380 | ||
381 | #### Verbose ping | |
382 | ||
383 | ```python | |
384 | from icmplib import * | |
385 | from time import sleep | |
386 | ||
387 | ||
388 | def verbose_ping(address, count=4, interval=1, timeout=2, id=PID): | |
389 | # A payload of 56 bytes is used by default. You can modify it using | |
390 | # the 'payload_size' parameter of your ICMP request. | |
391 | print(f'PING {address}: 56 data bytes\n') | |
392 | ||
393 | # We detect the socket to use from the specified IP address | |
394 | if is_ipv6_address(address): | |
395 | sock = ICMPv6Socket() | |
396 | else: | |
397 | sock = ICMPv4Socket() | |
398 | ||
399 | for sequence in range(count): | |
400 | # We create an ICMP request | |
401 | request = ICMPRequest( | |
402 | destination=address, | |
403 | id=id, | |
404 | sequence=sequence) | |
405 | ||
406 | try: | |
407 | # We send the request | |
408 | sock.send(request) | |
409 | ||
410 | # We are awaiting receipt of an ICMP reply | |
411 | reply = sock.receive(request, timeout) | |
412 | ||
413 | # We received a reply | |
414 | # We display some information | |
415 | print(f' {reply.bytes_received} bytes from ' | |
416 | f'{reply.source}: ', end='') | |
417 | ||
418 | # We throw an exception if it is an ICMP error message | |
419 | reply.raise_for_status() | |
420 | ||
421 | # We calculate the round-trip time and we display it | |
422 | round_trip_time = (reply.time - request.time) * 1000 | |
423 | ||
424 | print(f'icmp_seq={sequence} ' | |
425 | f'time={round(round_trip_time, 3)} ms') | |
426 | ||
427 | # We wait before continuing | |
428 | if sequence < count - 1: | |
429 | sleep(interval) | |
430 | ||
431 | except TimeoutExceeded: | |
432 | # The timeout has been reached | |
433 | print(f' Request timeout for icmp_seq {sequence}') | |
434 | ||
435 | except ICMPError as err: | |
436 | # An ICMP error message has been received | |
437 | print(err) | |
438 | ||
439 | except ICMPLibError: | |
440 | # All other errors | |
441 | print(' An error has occurred.') | |
442 | ||
443 | print('\nCompleted.') | |
444 | ||
445 | ||
446 | verbose_ping('1.1.1.1') | |
447 | ||
448 | # PING 1.1.1.1: 56 data bytes | |
449 | # | |
450 | # 64 bytes from 1.1.1.1: icmp_seq=0 time=12.061 ms | |
451 | # 64 bytes from 1.1.1.1: icmp_seq=1 time=12.597 ms | |
452 | # 64 bytes from 1.1.1.1: icmp_seq=2 time=12.475 ms | |
453 | # 64 bytes from 1.1.1.1: icmp_seq=3 time=10.822 ms | |
454 | # | |
455 | # Completed. | |
456 | ``` | |
457 | ||
458 | [`ICMPRequest`]: #ICMPRequest | |
459 | [`ICMPReply`]: #ICMPReply | |
460 | [`ICMPv4Socket`]: #ICMPv4Socket | |
461 | [`ICMPv6Socket`]: #ICMPv6Socket | |
462 | [`ICMPSocketError`]: 4-exceptions.md#ICMPSocketError | |
463 | [`SocketAddressError`]: 4-exceptions.md#SocketAddressError | |
464 | [`SocketPermissionError`]: 4-exceptions.md#SocketPermissionError | |
465 | [`SocketUnavailableError`]: 4-exceptions.md#SocketUnavailableError | |
466 | [`SocketBroadcastError`]: 4-exceptions.md#SocketBroadcastError | |
467 | [`TimeoutExceeded`]: 4-exceptions.md#TimeoutExceeded | |
468 | [`ICMPError`]: 4-exceptions.md#ICMPError | |
469 | [`DestinationUnreachable`]: 4-exceptions.md#DestinationUnreachable | |
470 | [`TimeExceeded`]: 4-exceptions.md#TimeExceeded |
0 | # Exceptions | |
1 | ||
2 | The library contains many exceptions to adapt to your needs: | |
3 | ||
4 | ``` | |
5 | ICMPLibError | |
6 | ├─ NameLookupError | |
7 | ├─ ICMPSocketError | |
8 | │ ├─ SocketAddressError | |
9 | │ ├─ SocketPermissionError | |
10 | │ ├─ SocketUnavailableError | |
11 | │ ├─ SocketBroadcastError | |
12 | │ └─ TimeoutExceeded | |
13 | │ | |
14 | └─ ICMPError | |
15 | ├─ DestinationUnreachable | |
16 | │ ├─ ICMPv4DestinationUnreachable | |
17 | │ └─ ICMPv6DestinationUnreachable | |
18 | │ | |
19 | └─ TimeExceeded | |
20 | ├─ ICMPv4TimeExceeded | |
21 | └─ ICMPv6TimeExceeded | |
22 | ``` | |
23 | ||
24 | ### ICMPLibError | |
25 | ||
26 | Exception class for the icmplib package. | |
27 | ||
28 | ### NameLookupError | |
29 | ||
30 | Raised when the requested name does not exist or cannot be resolved. This concerns both Fully Qualified Domain Names and hostnames. | |
31 | ||
32 | ### ICMPSocketError | |
33 | ||
34 | Base class for ICMP sockets exceptions. | |
35 | ||
36 | ### SocketAddressError | |
37 | ||
38 | Raised when the requested address cannot be assigned to the socket. | |
39 | ||
40 | ### SocketPermissionError | |
41 | ||
42 | Raised when the privileges are insufficient to create the socket. | |
43 | ||
44 | ### SocketUnavailableError | |
45 | ||
46 | Raised when an action is performed while the socket is closed. | |
47 | ||
48 | ### SocketBroadcastError | |
49 | ||
50 | Raised when a broadcast address is used and the corresponding option is not enabled on the socket. | |
51 | ||
52 | ### TimeoutExceeded | |
53 | ||
54 | Raised when a timeout occurs on a socket. | |
55 | ||
56 | ### ICMPError | |
57 | ||
58 | Base class for ICMP error messages. | |
59 | ||
60 | ### DestinationUnreachable | |
61 | ||
62 | Base class for ICMP Destination Unreachable messages. | |
63 | ||
64 | Destination Unreachable message is generated by the host or its inbound gateway to inform the client that the destination is unreachable for some reason. | |
65 | ||
66 | ### TimeExceeded | |
67 | ||
68 | Base class for ICMP Time Exceeded messages. | |
69 | ||
70 | Time Exceeded message is generated by a gateway to inform the source of a discarded datagram due to the time to live field reaching zero. A Time Exceeded message may also be sent by a host if it fails to reassemble a fragmented datagram within its time limit. |
0 | # Utilities | |
1 | ||
2 | ### resolve | |
3 | ||
4 | Resolve a hostname or FQDN to an IP address. Depending on the name specified in parameters, several IP addresses may be returned. | |
5 | ||
6 | This function relies on the DNS name server configured on your operating system. | |
7 | ||
8 | ```python | |
9 | resolve(name, family=None) | |
10 | ``` | |
11 | ||
12 | #### Parameters | |
13 | ||
14 | - `name` | |
15 | ||
16 | A hostname or a Fully Qualified Domain Name (FQDN). | |
17 | ||
18 | - Type: `str` | |
19 | ||
20 | - `family` | |
21 | ||
22 | The address family. Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, this function searches for IPv4 addresses first for compatibility reasons (A DNS lookup) before searching for IPv6 addresses (AAAA DNS lookup). | |
23 | ||
24 | - Type: `int` | |
25 | - Default: `None` | |
26 | ||
27 | #### Return value | |
28 | ||
29 | - A list of IP addresses corresponding to the name passed as a parameter. | |
30 | ||
31 | #### Exceptions | |
32 | ||
33 | - [`NameLookupError`] | |
34 | ||
35 | If you pass a hostname or FQDN in parameters and it does not exist or cannot be resolved. | |
36 | ||
37 | #### Example | |
38 | ||
39 | ```python | |
40 | >>> from icmplib import resolve | |
41 | ||
42 | >>> resolve('one.one.one.one') | |
43 | ['1.0.0.1', '1.1.1.1'] | |
44 | ||
45 | >>> resolve('localhost') | |
46 | ['127.0.0.1'] | |
47 | ||
48 | >>> resolve('ipv6.google.com') | |
49 | ['2a00:1450:4007:813::200e'] | |
50 | ``` | |
51 | ||
52 | <br> | |
53 | ||
54 | ### async_resolve | |
55 | ||
56 | Resolve a hostname or FQDN to an IP address. Depending on the name specified in parameters, several IP addresses may be returned. | |
57 | ||
58 | This function relies on the DNS name server configured on your operating system. | |
59 | ||
60 | *This function is non-blocking.* | |
61 | ||
62 | ```python | |
63 | async_resolve(name, family=None) | |
64 | ``` | |
65 | ||
66 | #### Parameters, return value and exceptions | |
67 | ||
68 | The same parameters, return values and exceptions as for the [`resolve`](#resolve) function. | |
69 | ||
70 | #### Example | |
71 | ||
72 | ```python | |
73 | >>> import asyncio | |
74 | >>> from icmplib import async_resolve | |
75 | ||
76 | >>> asyncio.run(async_resolve('one.one.one.one')) | |
77 | ['1.0.0.1', '1.1.1.1'] | |
78 | ||
79 | >>> asyncio.run(async_resolve('localhost')) | |
80 | ['127.0.0.1'] | |
81 | ||
82 | >>> asyncio.run(async_resolve('ipv6.google.com')) | |
83 | ['2a00:1450:4007:813::200e'] | |
84 | ``` | |
85 | ||
86 | <br> | |
87 | ||
88 | ### is_hostname | |
89 | ||
90 | Indicate whether the specified name is a hostname or an FQDN. | |
91 | ||
92 | ```python | |
93 | is_hostname(address) | |
94 | ``` | |
95 | ||
96 | #### Example | |
97 | ||
98 | ```python | |
99 | >>> from icmplib import is_hostname | |
100 | ||
101 | >>> is_hostname('one.one.one.one') | |
102 | True | |
103 | ||
104 | >>> is_hostname('localhost') | |
105 | True | |
106 | ||
107 | >>> is_hostname('127.0.0.1') | |
108 | False | |
109 | ||
110 | >>> is_hostname('2a00:1450:4007:813::200e') | |
111 | False | |
112 | ``` | |
113 | ||
114 | <br> | |
115 | ||
116 | ### is_ipv4_address | |
117 | ||
118 | Indicate whether the specified address is an IPv4 address. | |
119 | ||
120 | This function does not perform a strict checking. Its does not check if each byte of the IP address is within the allowed range. | |
121 | ||
122 | This function has been designed to be fast. | |
123 | ||
124 | ```python | |
125 | is_ipv4_address(address) | |
126 | ``` | |
127 | ||
128 | #### Example | |
129 | ||
130 | ```python | |
131 | >>> from icmplib import is_ipv4_address | |
132 | ||
133 | >>> is_ipv4_address('one.one.one.one') | |
134 | False | |
135 | ||
136 | >>> is_ipv4_address('localhost') | |
137 | False | |
138 | ||
139 | >>> is_ipv4_address('127.0.0.1') | |
140 | True | |
141 | ||
142 | >>> is_ipv4_address('2a00:1450:4007:813::200e') | |
143 | False | |
144 | ``` | |
145 | ||
146 | <br> | |
147 | ||
148 | ### is_ipv6_address | |
149 | ||
150 | Indicate whether the specified address is an IPv4 address. | |
151 | ||
152 | This function does not perform a strict checking. Its does not check if each byte of the IP address is within the allowed range. | |
153 | ||
154 | This function has been designed to be fast. | |
155 | ||
156 | ```python | |
157 | is_ipv6_address(address) | |
158 | ``` | |
159 | ||
160 | #### Example | |
161 | ||
162 | ```python | |
163 | >>> from icmplib import is_ipv6_address | |
164 | ||
165 | >>> is_ipv6_address('one.one.one.one') | |
166 | False | |
167 | ||
168 | >>> is_ipv6_address('localhost') | |
169 | False | |
170 | ||
171 | >>> is_ipv6_address('127.0.0.1') | |
172 | False | |
173 | ||
174 | >>> is_ipv6_address('2a00:1450:4007:813::200e') | |
175 | True | |
176 | ``` | |
177 | ||
178 | [`NameLookupError`]: 4-exceptions.md#NameLookupError |
0 | # Use icmplib without root privileges | |
1 | ||
2 | - **Step 1: adapt your code** | |
3 | ||
4 | To use icmplib without root privileges, you must set the `privileged` parameter to `False` on the [`ping`] and [`multiping`] functions, as well as their asynchronous variants and the low level classes. | |
5 | ||
6 | By disabling this parameter, icmplib let the kernel handle some parts of the ICMP headers. | |
7 | ||
8 | The [`traceroute`] function does not have this parameter. It should always be run as root to receive ICMP Time Exceeded messages from gateways. | |
9 | ||
10 | - **Step 2: allow this feature on your operating system** | |
11 | ||
12 | On some Linux systems, you must allow this feature: | |
13 | ||
14 | ```shell | |
15 | $ echo 'net.ipv4.ping_group_range = 0 2147483647' | sudo tee -a /etc/sysctl.conf | |
16 | $ sudo sysctl -p | |
17 | ``` | |
18 | ||
19 | You can check the current value with the following command: | |
20 | ||
21 | ```shell | |
22 | $ sysctl net.ipv4.ping_group_range | |
23 | net.ipv4.ping_group_range = 0 2147483647 | |
24 | ``` | |
25 | ||
26 | *Since Ubuntu 20.04 LTS, this manipulation is no longer necessary.* | |
27 | ||
28 | [Read more about `ping_group_range` on www.kernel.org] | |
29 | ||
30 | [`ping`]: 2-functions.md#ping | |
31 | [`multiping`]: 2-functions.md#multiping | |
32 | [`traceroute`]: 2-functions.md#traceroute | |
33 | [Read more about `ping_group_range` on www.kernel.org]: https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt |
0 | # Documentation | |
1 | ||
2 | **[Installation](1-installation.md)** | |
3 | - [Requirements](1-installation.md#requirements) | |
4 | - [Installation and update](1-installation.md#installation-and-update) | |
5 | - [Imports](1-installation.md#imports) | |
6 | ||
7 | **[Built-in functions](2-functions.md)** | |
8 | - [`ping`](2-functions.md#ping) | |
9 | - [`multiping`](2-functions.md#multiping) | |
10 | - [`traceroute`](2-functions.md#traceroute) | |
11 | - [`async_ping`](2-functions.md#async_ping) | |
12 | - [`async_multiping`](2-functions.md#async_multiping) | |
13 | ||
14 | **[Sockets and classes](3-sockets.md)** | |
15 | - [`ICMPRequest`](3-sockets.md#ICMPRequest) | |
16 | - [`ICMPReply`](3-sockets.md#ICMPReply) | |
17 | - [`ICMPv4Socket`](3-sockets.md#ICMPv4Socket) | |
18 | - [`ICMPv6Socket`](3-sockets.md#ICMPv6Socket) | |
19 | - [`AsyncSocket`](3-sockets.md#AsyncSocket) | |
20 | - [Examples](3-sockets.md#examples) | |
21 | ||
22 | **[Exceptions](4-exceptions.md)** | |
23 | - [`ICMPLibError`](4-exceptions.md#ICMPLibError) | |
24 | - [`NameLookupError`](4-exceptions.md#NameLookupError) | |
25 | - [`ICMPSocketError`](4-exceptions.md#ICMPSocketError) | |
26 | - [`SocketAddressError`](4-exceptions.md#SocketAddressError) | |
27 | - [`SocketPermissionError`](4-exceptions.md#SocketPermissionError) | |
28 | - [`SocketUnavailableError`](4-exceptions.md#SocketUnavailableError) | |
29 | - [`SocketBroadcastError`](4-exceptions.md#SocketBroadcastError) | |
30 | - [`TimeoutExceeded`](4-exceptions.md#TimeoutExceeded) | |
31 | - [`ICMPError`](4-exceptions.md#ICMPError) | |
32 | - [`DestinationUnreachable`](4-exceptions.md#DestinationUnreachable) | |
33 | - [`TimeExceeded`](4-exceptions.md#TimeExceeded) | |
34 | ||
35 | **[Utilities](5-utilities.md)** | |
36 | - [`resolve`](5-utilities.md#resolve) | |
37 | - [`async_resolve`](5-utilities.md#async_resolve) | |
38 | - [`is_hostname`](5-utilities.md#is_hostname) | |
39 | - [`is_ipv4_address`](5-utilities.md#is_ipv4_address) | |
40 | - [`is_ipv6_address`](5-utilities.md#is_ipv6_address) | |
41 | ||
42 | **[Use icmplib without root privileges](6-use-icmplib-without-privileges.md)** | |
43 | ||
44 | **[Examples](../examples)** |
20 | 20 | 64 bytes from 1.1.1.1: icmp_seq=1 time=12.597 ms |
21 | 21 | 64 bytes from 1.1.1.1: icmp_seq=2 time=12.475 ms |
22 | 22 | 64 bytes from 1.1.1.1: icmp_seq=3 time=10.822 ms |
23 | ||
24 | Completed. | |
23 | 25 | ``` |
24 | 26 | |
25 | 27 | - [verbose-traceroute](verbose_traceroute.py) |
32 | 34 | Traceroute to ovh.com (198.27.92.1): 56 data bytes, 30 hops max |
33 | 35 | |
34 | 36 | 1 192.168.0.254 192.168.0.254 9.86 ms |
35 | 2 194.149.164.56 194.149.164.56 4.6 ms | |
36 | 3 213.186.32.181 be100-159.th2-1-a9.fr.eu 11.99 ms | |
37 | 4 94.23.122.146 be102.rbx-g1-nc5.fr.eu 7.81 ms | |
37 | 2 194.149.164.56 194.149.164.56 4.61 ms | |
38 | 3 213.186.32.181 be100-159.th2-1-a9.fr.eu 11.97 ms | |
39 | 4 94.23.122.146 be102.rbx-g1-nc5.fr.eu 15.81 ms | |
38 | 40 | 5 * * * |
39 | 6 37.187.231.75 be5.rbx-iplb1-a70.fr.eu 17.1 ms | |
41 | 6 37.187.231.75 be5.rbx-iplb1-a70.fr.eu 17.12 ms | |
40 | 42 | 7 198.27.92.1 www.ovh.com 10.87 ms |
43 | ||
44 | Completed. | |
41 | 45 | ``` |
42 | 46 | |
43 | 47 | - [broadcast-ping](broadcast_ping.py) |
64 | 68 | 64 bytes from 10.0.0.17: icmp_seq=3 time=1.112 ms |
65 | 69 | 64 bytes from 10.0.0.40: icmp_seq=3 time=1.384 ms |
66 | 70 | 64 bytes from 10.0.0.41: icmp_seq=3 time=9.565 ms |
71 | ||
72 | Completed. | |
67 | 73 | ``` |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
15 | 17 | 64 bytes from 10.0.0.17: icmp_seq=0 time=1.065 ms |
16 | 18 | 64 bytes from 10.0.0.40: icmp_seq=0 time=1.595 ms |
17 | 19 | 64 bytes from 10.0.0.41: icmp_seq=0 time=9.471 ms |
18 | ||
19 | 20 | 64 bytes from 10.0.0.17: icmp_seq=1 time=0.983 ms |
20 | 21 | 64 bytes from 10.0.0.40: icmp_seq=1 time=1.579 ms |
21 | 22 | 64 bytes from 10.0.0.41: icmp_seq=1 time=9.345 ms |
22 | ||
23 | 23 | 64 bytes from 10.0.0.17: icmp_seq=2 time=0.916 ms |
24 | 24 | 64 bytes from 10.0.0.40: icmp_seq=2 time=2.031 ms |
25 | 25 | 64 bytes from 10.0.0.41: icmp_seq=2 time=9.554 ms |
26 | ||
27 | 26 | 64 bytes from 10.0.0.17: icmp_seq=3 time=1.112 ms |
28 | 27 | 64 bytes from 10.0.0.40: icmp_seq=3 time=1.384 ms |
29 | 28 | 64 bytes from 10.0.0.41: icmp_seq=3 time=9.565 ms |
29 | ||
30 | Completed. | |
30 | 31 | ''' |
31 | 32 | |
32 | from icmplib import ( | |
33 | ICMPv4Socket, | |
34 | ICMPRequest, | |
35 | TimeoutExceeded, | |
36 | ICMPLibError, | |
37 | PID) | |
33 | from icmplib import ICMPv4Socket, ICMPRequest | |
34 | from icmplib import ICMPLibError, TimeoutExceeded, PID | |
38 | 35 | |
39 | 36 | |
40 | 37 | def broadcast_ping(address, count=4, timeout=1, id=PID): |
41 | # ICMPRequest uses a payload of 56 bytes by default | |
42 | # You can modify it using the payload_size parameter | |
43 | print(f'PING {address}: 56 data bytes') | |
38 | # A payload of 56 bytes is used by default. You can modify it using | |
39 | # the 'payload_size' parameter of your ICMP request. | |
40 | print(f'PING {address}: 56 data bytes\n') | |
44 | 41 | |
45 | 42 | # Broadcast is only possible in IPv4 |
46 | socket = ICMPv4Socket() | |
43 | sock = ICMPv4Socket() | |
47 | 44 | |
48 | 45 | # We allow the socket to send broadcast packets |
49 | socket.broadcast = True | |
46 | sock.broadcast = True | |
50 | 47 | |
51 | 48 | for sequence in range(count): |
52 | 49 | # We create an ICMP request |
53 | 50 | request = ICMPRequest( |
54 | 51 | destination=address, |
55 | 52 | id=id, |
56 | sequence=sequence, | |
57 | timeout=timeout) | |
58 | ||
59 | print() | |
53 | sequence=sequence) | |
60 | 54 | |
61 | 55 | try: |
62 | 56 | # We send the request |
63 | socket.send(request) | |
57 | sock.send(request) | |
64 | 58 | |
65 | 59 | while 'we receive replies': |
66 | # We are awaiting receipt of an ICMP reply | |
67 | # If there is no more responses, the TimeoutExceeded | |
68 | # exception is thrown and the loop is stopped | |
69 | reply = socket.receive() | |
60 | # We are awaiting receipt of an ICMP reply. If there is | |
61 | # no more responses, the 'TimeoutExceeded' exception is | |
62 | # thrown and the loop is stopped. | |
63 | reply = sock.receive(request, timeout) | |
70 | 64 | |
71 | # We calculate the round-trip time of the reply | |
65 | # We calculate the round-trip time | |
72 | 66 | round_trip_time = (reply.time - request.time) * 1000 |
73 | 67 | |
74 | 68 | # We display some information |
83 | 77 | |
84 | 78 | except ICMPLibError: |
85 | 79 | # All other errors |
86 | print('An error has occurred.') | |
80 | print(' An error has occurred.') | |
81 | ||
82 | print('\nCompleted.') | |
87 | 83 | |
88 | 84 | |
89 | 85 | # Limited broadcast |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
11 | 13 | Example: multiping |
12 | 14 | ''' |
13 | 15 | |
14 | from icmplib import multiping | |
16 | from icmplib import resolve, multiping | |
15 | 17 | |
16 | 18 | |
17 | 19 | addresses = [ |
18 | # A fully qualified domain name (FQDN) is allowed. The first | |
19 | # address returned from the DNS resolution will be used. | |
20 | # For deterministic behavior, prefer to use an IP address. | |
21 | 'github.com', | |
22 | ||
23 | 20 | # IPv4 addresses |
24 | 21 | '1.1.1.1', |
25 | 22 | '8.8.8.8', |
28 | 25 | |
29 | 26 | # IPv6 addresses |
30 | 27 | '::1', |
28 | ||
29 | # Hostnames and Fully Qualified Domain Names (FQDNs) are allowed but | |
30 | # not recommended. You can easily retrieve their IP address by | |
31 | # calling the built-in 'resolve' function. For deterministic | |
32 | # behavior, prefer to use an IP address. | |
33 | 'github.com' | |
31 | 34 | ] |
32 | 35 | |
33 | hosts = multiping(addresses, count=2, interval=0.5, timeout=1) | |
36 | hosts = multiping(addresses, count=2, timeout=1) | |
34 | 37 | |
35 | 38 | hosts_alive = [] |
36 | 39 | hosts_dead = [] |
38 | 41 | for host in hosts: |
39 | 42 | if host.is_alive: |
40 | 43 | hosts_alive.append(host.address) |
41 | ||
42 | 44 | else: |
43 | 45 | hosts_dead.append(host.address) |
44 | 46 | |
45 | 47 | print(hosts_alive) |
46 | # ['github.com', '1.1.1.1', '8.8.8.8', '::1'] | |
48 | # ['1.1.1.1', '8.8.8.8', '::1', '140.82.121.4'] | |
47 | 49 | |
48 | 50 | print(hosts_dead) |
49 | 51 | # ['10.0.0.100', '10.0.0.200'] |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
16 | 18 | |
17 | 19 | host = ping('1.1.1.1', count=10, interval=0.5, timeout=1) |
18 | 20 | |
19 | # The IP address of the gateway or host that responded to the request | |
21 | # The IP address of the host that responded to the request | |
20 | 22 | print(host.address) |
21 | 23 | # '1.1.1.1' |
22 | 24 | |
23 | # The minimum round-trip time | |
25 | # The minimum round-trip time in milliseconds | |
24 | 26 | print(host.min_rtt) |
25 | # 12.2 | |
27 | # 5.761 | |
26 | 28 | |
27 | # The average round-trip time | |
29 | # The average round-trip time in milliseconds | |
28 | 30 | print(host.avg_rtt) |
29 | # 13.2 | |
31 | # 12.036 | |
30 | 32 | |
31 | # The maximum round-trip time | |
33 | # The maximum round-trip time in milliseconds | |
32 | 34 | print(host.max_rtt) |
33 | # 17.6 | |
35 | # 16.207 | |
34 | 36 | |
35 | # The number of packets transmitted to the destination host | |
37 | # The list of round-trip times expressed in milliseconds | |
38 | print(host.rtts) | |
39 | # [ 11.595010757446289, 13.135194778442383, 9.614229202270508, | |
40 | # 16.018152236938477, 11.960029602050781, 5.761146545410156, | |
41 | # 16.207218170166016, 11.937141418457031, 12.098073959350586 ] | |
42 | ||
43 | # The number of requests transmitted to the remote host | |
36 | 44 | print(host.packets_sent) |
37 | 45 | # 10 |
38 | 46 | |
39 | # The number of packets sent by the remote host and received by the | |
40 | # current host | |
47 | # The number of ICMP responses received from the remote host | |
41 | 48 | print(host.packets_received) |
42 | 49 | # 9 |
43 | 50 | |
46 | 53 | print(host.packet_loss) |
47 | 54 | # 0.1 |
48 | 55 | |
56 | # The jitter in milliseconds, defined as the variance of the latency of | |
57 | # packets flowing through the network | |
58 | # At least two ICMP responses are required to calculate the jitter | |
59 | print(host.jitter) | |
60 | # 4.575 | |
61 | ||
49 | 62 | # Indicates whether the host is reachable |
50 | 63 | print(host.is_alive) |
51 | 64 | # True |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
14 | 16 | from icmplib import traceroute |
15 | 17 | |
16 | 18 | |
17 | hops = traceroute('1.1.1.1', timeout=1, fast_mode=True) | |
19 | hops = traceroute('1.1.1.1', timeout=1, fast=True) | |
18 | 20 | |
19 | 21 | print(hops) |
20 | # [<Hop 1 [192.168.0.254]>, <Hop 2 [194.149.169.162]>, | |
21 | # <Hop 4 [149.11.115.13]>, <Hop 5 [154.54.61.21]>, | |
22 | # <Hop 6 [154.54.60.126]>, <Hop 7 [149.11.0.126]>, | |
23 | # <Hop 8 [1.1.1.1]>] | |
24 | ||
22 | # [ <Hop 1 [10.0.0.1]>, <Hop 2 [194.149.169.49]>, | |
23 | # <Hop 3 [194.149.166.54]>, <Hop 5 [212.73.205.22]>, | |
24 | # <Hop 6 [1.1.1.1]> ] | |
25 | 25 | |
26 | 26 | last_distance = 0 |
27 | 27 | |
28 | 28 | for hop in hops: |
29 | 29 | if last_distance + 1 != hop.distance: |
30 | print(' * Some routers are not responding') | |
30 | print(' * Some gateways are not responding') | |
31 | 31 | |
32 | print(f'{hop.distance:4} {hop.address:15} ' | |
32 | print(f' {hop.distance:<2} {hop.address:15} ' | |
33 | 33 | f'{hop.avg_rtt} ms') |
34 | 34 | |
35 | 35 | last_distance = hop.distance |
36 | 36 | |
37 | # 1 192.168.0.254 11.327 ms | |
38 | # 2 194.149.169.162 16.354 ms | |
39 | # * Some routers are not responding | |
40 | # 4 149.11.115.13 11.498 ms | |
41 | # 5 154.54.61.21 4.335 ms | |
42 | # 6 154.54.60.126 5.645 ms | |
43 | # 7 149.11.0.126 5.873 ms | |
44 | # 8 1.1.1.1 4.561 ms | |
37 | # 1 10.0.0.1 5.196 ms | |
38 | # 2 194.149.169.49 7.552 ms | |
39 | # 3 194.149.166.54 12.21 ms | |
40 | # * Some gateways are not responding | |
41 | # 5 212.73.205.22 22.15 ms | |
42 | # 6 1.1.1.1 13.59 ms |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
16 | 18 | 64 bytes from 1.1.1.1: icmp_seq=1 time=12.597 ms |
17 | 19 | 64 bytes from 1.1.1.1: icmp_seq=2 time=12.475 ms |
18 | 20 | 64 bytes from 1.1.1.1: icmp_seq=3 time=10.822 ms |
21 | ||
22 | Completed. | |
19 | 23 | ''' |
20 | ||
21 | from icmplib import ( | |
22 | ICMPv4Socket, | |
23 | ICMPv6Socket, | |
24 | ICMPRequest, | |
25 | TimeoutExceeded, | |
26 | ICMPError, | |
27 | ICMPLibError, | |
28 | is_ipv6_address, | |
29 | PID) | |
30 | 24 | |
31 | 25 | from time import sleep |
32 | 26 | |
27 | from icmplib import ICMPv4Socket, ICMPv6Socket, ICMPRequest | |
28 | from icmplib import ICMPLibError, ICMPError, TimeoutExceeded | |
29 | from icmplib import PID, is_ipv6_address | |
30 | ||
33 | 31 | |
34 | 32 | def verbose_ping(address, count=4, interval=1, timeout=2, id=PID): |
35 | # ICMPRequest uses a payload of 56 bytes by default | |
36 | # You can modify it using the payload_size parameter | |
33 | # A payload of 56 bytes is used by default. You can modify it using | |
34 | # the 'payload_size' parameter of your ICMP request. | |
37 | 35 | print(f'PING {address}: 56 data bytes\n') |
38 | 36 | |
39 | # Detection of the socket to use | |
37 | # We detect the socket to use from the specified IP address | |
40 | 38 | if is_ipv6_address(address): |
41 | socket = ICMPv6Socket() | |
42 | ||
39 | sock = ICMPv6Socket() | |
43 | 40 | else: |
44 | socket = ICMPv4Socket() | |
41 | sock = ICMPv4Socket() | |
45 | 42 | |
46 | 43 | for sequence in range(count): |
47 | 44 | # We create an ICMP request |
48 | 45 | request = ICMPRequest( |
49 | 46 | destination=address, |
50 | 47 | id=id, |
51 | sequence=sequence, | |
52 | timeout=timeout) | |
48 | sequence=sequence) | |
53 | 49 | |
54 | 50 | try: |
55 | 51 | # We send the request |
56 | socket.send(request) | |
52 | sock.send(request) | |
57 | 53 | |
58 | 54 | # We are awaiting receipt of an ICMP reply |
59 | reply = socket.receive() | |
55 | reply = sock.receive(request, timeout) | |
60 | 56 | |
61 | 57 | # We received a reply |
62 | 58 | # We display some information |
72 | 68 | print(f'icmp_seq={sequence} ' |
73 | 69 | f'time={round(round_trip_time, 3)} ms') |
74 | 70 | |
75 | # We pause before continuing | |
71 | # We wait before continuing | |
76 | 72 | if sequence < count - 1: |
77 | 73 | sleep(interval) |
78 | 74 | |
79 | 75 | except TimeoutExceeded: |
80 | 76 | # The timeout has been reached |
81 | print(f'Request timeout for icmp_seq {sequence}') | |
77 | print(f' Request timeout for icmp_seq {sequence}') | |
82 | 78 | |
83 | 79 | except ICMPError as err: |
84 | 80 | # An ICMP error message has been received |
86 | 82 | |
87 | 83 | except ICMPLibError: |
88 | 84 | # All other errors |
89 | print('An error has occurred.') | |
85 | print(' An error has occurred.') | |
86 | ||
87 | print('\nCompleted.') | |
90 | 88 | |
91 | 89 | |
92 | 90 | verbose_ping('1.1.1.1') |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
13 | 15 | Traceroute to ovh.com (198.27.92.1): 56 data bytes, 30 hops max |
14 | 16 | |
15 | 17 | 1 192.168.0.254 192.168.0.254 9.86 ms |
16 | 2 194.149.164.56 194.149.164.56 4.6 ms | |
17 | 3 213.186.32.181 be100-159.th2-1-a9.fr.eu 11.99 ms | |
18 | 4 94.23.122.146 be102.rbx-g1-nc5.fr.eu 7.81 ms | |
18 | 2 194.149.164.56 194.149.164.56 4.61 ms | |
19 | 3 213.186.32.181 be100-159.th2-1-a9.fr.eu 11.97 ms | |
20 | 4 94.23.122.146 be102.rbx-g1-nc5.fr.eu 15.81 ms | |
19 | 21 | 5 * * * |
20 | 6 37.187.231.75 be5.rbx-iplb1-a70.fr.eu 17.1 ms | |
22 | 6 37.187.231.75 be5.rbx-iplb1-a70.fr.eu 17.12 ms | |
21 | 23 | 7 198.27.92.1 www.ovh.com 10.87 ms |
24 | ||
25 | Completed. | |
22 | 26 | ''' |
23 | 27 | |
24 | from icmplib import ( | |
25 | ICMPv4Socket, | |
26 | ICMPv6Socket, | |
27 | ICMPRequest, | |
28 | TimeoutExceeded, | |
29 | TimeExceeded, | |
30 | ICMPLibError, | |
31 | is_ipv6_address, | |
32 | PID) | |
33 | ||
34 | from socket import getfqdn, gethostbyname | |
28 | from socket import getfqdn | |
35 | 29 | from time import sleep |
36 | 30 | |
31 | from icmplib import ICMPv4Socket, ICMPv6Socket, ICMPRequest | |
32 | from icmplib import ICMPLibError, TimeoutExceeded, TimeExceeded | |
33 | from icmplib import PID, resolve, is_hostname, is_ipv6_address | |
37 | 34 | |
38 | def verbose_traceroute(address, count=3, interval=0.05, timeout=2, | |
35 | ||
36 | def verbose_traceroute(address, count=2, interval=0.05, timeout=2, | |
39 | 37 | id=PID, max_hops=30): |
40 | # ICMPRequest uses a payload of 56 bytes by default | |
41 | # You can modify it using the payload_size parameter | |
42 | print(f'Traceroute to {address} ({gethostbyname(address)}): ' | |
38 | # We perform a DNS lookup if a hostname or an FQDN is passed in | |
39 | # parameters. | |
40 | if is_hostname(address): | |
41 | ip_address = resolve(address)[0] | |
42 | else: | |
43 | ip_address = address | |
44 | ||
45 | # A payload of 56 bytes is used by default. You can modify it using | |
46 | # the 'payload_size' parameter of your ICMP request. | |
47 | print(f'Traceroute to {address} ({ip_address}): ' | |
43 | 48 | f'56 data bytes, {max_hops} hops max\n') |
44 | 49 | |
45 | # Detection of the socket to use | |
46 | if is_ipv6_address(address): | |
47 | socket = ICMPv6Socket() | |
48 | ||
50 | # We detect the socket to use from the specified IP address | |
51 | if is_ipv6_address(ip_address): | |
52 | sock = ICMPv6Socket() | |
49 | 53 | else: |
50 | socket = ICMPv4Socket() | |
54 | sock = ICMPv4Socket() | |
51 | 55 | |
52 | 56 | ttl = 1 |
53 | 57 | host_reached = False |
56 | 60 | for sequence in range(count): |
57 | 61 | # We create an ICMP request |
58 | 62 | request = ICMPRequest( |
59 | destination=address, | |
63 | destination=ip_address, | |
60 | 64 | id=id, |
61 | 65 | sequence=sequence, |
62 | timeout=timeout, | |
63 | 66 | ttl=ttl) |
64 | 67 | |
65 | 68 | try: |
66 | 69 | # We send the request |
67 | socket.send(request) | |
70 | sock.send(request) | |
68 | 71 | |
69 | 72 | # We are awaiting receipt of an ICMP reply |
70 | reply = socket.receive() | |
73 | reply = sock.receive(request, timeout) | |
71 | 74 | |
72 | 75 | # We received a reply |
73 | 76 | # We display some information |
74 | 77 | source_name = getfqdn(reply.source) |
75 | 78 | |
76 | print(f'{ttl:3} {reply.source:15} ' | |
79 | print(f' {ttl:<2} {reply.source:15} ' | |
77 | 80 | f'{source_name:40} ', end='') |
78 | 81 | |
79 | 82 | # We throw an exception if it is an ICMP error message |
103 | 106 | |
104 | 107 | except TimeoutExceeded: |
105 | 108 | # The timeout has been reached and no host or gateway |
106 | # has responded after multiple attemps | |
109 | # has responded after multiple attempts | |
107 | 110 | if sequence >= count - 1: |
108 | print(f'{ttl:3} * * *') | |
111 | print(f' {ttl:<2} * * *') | |
109 | 112 | |
110 | 113 | except ICMPLibError: |
111 | 114 | # Other errors are ignored |
113 | 116 | |
114 | 117 | ttl += 1 |
115 | 118 | |
116 | print() | |
119 | print('\nCompleted.') | |
117 | 120 | |
118 | 121 | |
122 | # This function supports both FQDNs and IP addresses. See the 'resolve' | |
123 | # function for details. | |
119 | 124 | verbose_traceroute('ovh.com') |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
23 | 25 | <https://www.gnu.org/licenses/>. |
24 | 26 | ''' |
25 | 27 | |
26 | from .sockets import ICMPv4Socket, ICMPv6Socket | |
28 | from .sockets import ICMPv4Socket, ICMPv6Socket, AsyncSocket | |
27 | 29 | from .models import ICMPRequest, ICMPReply, Host, Hop |
28 | from .ping import ping, multiping | |
30 | from .ping import ping, async_ping | |
31 | from .multiping import multiping, async_multiping | |
29 | 32 | from .traceroute import traceroute |
30 | 33 | from .exceptions import * |
31 | from .utils import PID, is_ipv4_address, is_ipv6_address | |
34 | from .utils import is_hostname, is_ipv4_address, is_ipv6_address | |
35 | from .utils import PID, resolve, async_resolve | |
32 | 36 | |
33 | 37 | |
34 | 38 | __author__ = 'Valentin BELYN' |
35 | __copyright__ = 'Copyright 2017-2020 Valentin BELYN' | |
39 | __copyright__ = 'Copyright 2017-2021 Valentin BELYN' | |
36 | 40 | __license__ = 'GNU Lesser General Public License v3.0' |
37 | 41 | |
38 | __version__ = '1.2.2' | |
39 | __build__ = '201010' | |
42 | __version__ = '3.0.1' | |
43 | __build__ = '210814' |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
29 | 31 | Exception class for the icmplib package. |
30 | 32 | |
31 | 33 | ''' |
32 | def __init__(self, message): | |
33 | self._message = message | |
34 | ||
35 | def __str__(self): | |
36 | return self._message | |
37 | ||
38 | @property | |
39 | def message(self): | |
40 | return self._message | |
34 | ||
35 | ||
36 | class NameLookupError(ICMPLibError): | |
37 | ''' | |
38 | Raised when the requested name does not exist or cannot be resolved. | |
39 | This concerns both Fully Qualified Domain Names and hostnames. | |
40 | ||
41 | ''' | |
42 | def __init__(self, name): | |
43 | message = f'The name \'{name}\' cannot be resolved' | |
44 | super().__init__(message) | |
41 | 45 | |
42 | 46 | |
43 | 47 | class ICMPSocketError(ICMPLibError): |
47 | 51 | ''' |
48 | 52 | |
49 | 53 | |
54 | class SocketAddressError(ICMPSocketError): | |
55 | ''' | |
56 | Raised when the requested address cannot be assigned to the socket. | |
57 | ||
58 | ''' | |
59 | def __init__(self, address): | |
60 | message = f'The requested address ({address}) cannot be ' \ | |
61 | 'assigned to the socket' | |
62 | super().__init__(message) | |
63 | ||
64 | ||
50 | 65 | class SocketPermissionError(ICMPSocketError): |
51 | 66 | ''' |
52 | Raised when the permissions are insufficient to create a socket. | |
53 | ||
54 | ''' | |
55 | def __init__(self): | |
56 | message = 'Root privileges are required to create the socket' | |
67 | Raised when the privileges are insufficient to create the socket. | |
68 | ||
69 | ''' | |
70 | def __init__(self, privileged): | |
71 | if privileged: | |
72 | message = 'Root privileges are required to create the socket' | |
73 | else: | |
74 | message = 'A prior configuration of your OS is required ' \ | |
75 | 'to use ICMP sockets without root privileges. ' \ | |
76 | 'Read more on https://github.com/ValentinBELYN' \ | |
77 | '/icmplib' | |
78 | ||
57 | 79 | super().__init__(message) |
58 | 80 | |
59 | 81 | |
69 | 91 | |
70 | 92 | class SocketBroadcastError(ICMPSocketError): |
71 | 93 | ''' |
72 | Raised when a broadcast address is used and the corresponding | |
73 | option is not enabled on the socket. | |
94 | Raised when a broadcast address is used and the corresponding option | |
95 | is not enabled on the socket. | |
74 | 96 | |
75 | 97 | ''' |
76 | 98 | def __init__(self): |
77 | 99 | message = 'Broadcast is not allowed: ' \ |
78 | 'please use broadcast method (setter) to allow it' | |
100 | 'please use the \'broadcast\' property to allow it' | |
79 | 101 | super().__init__(message) |
80 | 102 | |
81 | 103 | |
117 | 139 | def __init__(self, reply): |
118 | 140 | if reply.code in self._CODES: |
119 | 141 | message = self._CODES[reply.code] |
120 | ||
121 | 142 | else: |
122 | 143 | message = f'Destination unreachable, bad code: {reply.code}' |
123 | 144 | |
161 | 182 | ''' |
162 | 183 | Base class for ICMP Time Exceeded messages. |
163 | 184 | |
164 | Time Exceeded message is generated by a gateway to inform the | |
165 | source of a discarded datagram due to the time to live field | |
166 | reaching zero. A Time Exceeded message may also be sent by a host | |
167 | if it fails to reassemble a fragmented datagram within its time | |
168 | limit. | |
185 | Time Exceeded message is generated by a gateway to inform the source | |
186 | of a discarded datagram due to the time to live field reaching zero. | |
187 | A Time Exceeded message may also be sent by a host if it fails to | |
188 | reassemble a fragmented datagram within its time limit. | |
169 | 189 | |
170 | 190 | ''' |
171 | 191 | _CODES = {} |
173 | 193 | def __init__(self, reply): |
174 | 194 | if reply.code in self._CODES: |
175 | 195 | message = self._CODES[reply.code] |
176 | ||
177 | 196 | else: |
178 | 197 | message = f'Time exceeded, bad code: {reply.code}' |
179 | 198 |
0 | ''' | |
1 | icmplib | |
2 | ~~~~~~~ | |
3 | ||
4 | https://github.com/ValentinBELYN/icmplib | |
5 | ||
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
7 | :license: GNU LGPLv3, see the LICENSE for details. | |
8 | ||
9 | ~~~~~~~ | |
10 | ||
11 | This program is free software: you can redistribute it and/or | |
12 | modify it under the terms of the GNU Lesser General Public License | |
13 | as published by the Free Software Foundation, either version 3 of | |
14 | the License, or (at your option) any later version. | |
15 | ||
16 | This program is distributed in the hope that it will be useful, | |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | GNU Lesser General Public License for more details. | |
20 | ||
21 | You should have received a copy of the GNU Lesser General Public | |
22 | License along with this program. If not, see | |
23 | <https://www.gnu.org/licenses/>. | |
24 | ''' | |
25 | ||
26 | import socket | |
27 | from .utils import PLATFORM_WINDOWS | |
28 | ||
29 | ||
30 | # Fix for Windows | |
31 | if PLATFORM_WINDOWS: | |
32 | socket.IPPROTO_IPV6 = 41 | |
33 | ||
34 | ||
35 | class IPSocket: | |
36 | def __init__(self, family, protocol): | |
37 | self._socket = socket.socket( | |
38 | family=family, | |
39 | type=socket.SOCK_RAW, | |
40 | proto=protocol) | |
41 | ||
42 | self.timeout = 5 | |
43 | self.ttl = 64 | |
44 | self.traffic_class = 0 | |
45 | ||
46 | def send(self, payload, address, port): | |
47 | return self._socket.sendto(payload, (address, port)) | |
48 | ||
49 | def receive(self, buffer_size=1024): | |
50 | packet = self._socket.recvfrom(buffer_size) | |
51 | ||
52 | payload = packet[0] | |
53 | address = packet[1][0] | |
54 | port = packet[1][1] | |
55 | ||
56 | return payload, address, port | |
57 | ||
58 | def close(self): | |
59 | self._socket.close() | |
60 | ||
61 | @property | |
62 | def timeout(self): | |
63 | return self._timeout | |
64 | ||
65 | @timeout.setter | |
66 | def timeout(self, timeout): | |
67 | self._socket.settimeout(timeout) | |
68 | self._timeout = timeout | |
69 | ||
70 | @property | |
71 | def ttl(self): | |
72 | return self._ttl | |
73 | ||
74 | @ttl.setter | |
75 | def ttl(self, ttl): | |
76 | self._ttl = ttl | |
77 | ||
78 | @property | |
79 | def traffic_class(self): | |
80 | return self._traffic_class | |
81 | ||
82 | @traffic_class.setter | |
83 | def traffic_class(self, traffic_class): | |
84 | self._traffic_class = traffic_class | |
85 | ||
86 | ||
87 | class IPv4Socket(IPSocket): | |
88 | def __init__(self, protocol): | |
89 | super().__init__( | |
90 | family=socket.AF_INET, | |
91 | protocol=protocol) | |
92 | ||
93 | self._broadcast = False | |
94 | ||
95 | @IPSocket.ttl.setter | |
96 | def ttl(self, ttl): | |
97 | self._ttl = ttl | |
98 | ||
99 | self._socket.setsockopt( | |
100 | socket.IPPROTO_IP, | |
101 | socket.IP_TTL, | |
102 | ttl) | |
103 | ||
104 | @IPSocket.traffic_class.setter | |
105 | def traffic_class(self, traffic_class): | |
106 | self._traffic_class = traffic_class | |
107 | ||
108 | if not PLATFORM_WINDOWS: | |
109 | self._socket.setsockopt( | |
110 | socket.IPPROTO_IP, | |
111 | socket.IP_TOS, | |
112 | traffic_class) | |
113 | ||
114 | @property | |
115 | def broadcast(self): | |
116 | return self._broadcast | |
117 | ||
118 | @broadcast.setter | |
119 | def broadcast(self, allow): | |
120 | self._broadcast = allow | |
121 | ||
122 | self._socket.setsockopt( | |
123 | socket.SOL_SOCKET, | |
124 | socket.SO_BROADCAST, | |
125 | allow) | |
126 | ||
127 | ||
128 | class IPv6Socket(IPSocket): | |
129 | def __init__(self, protocol): | |
130 | super().__init__( | |
131 | family=socket.AF_INET6, | |
132 | protocol=protocol) | |
133 | ||
134 | @IPSocket.ttl.setter | |
135 | def ttl(self, ttl): | |
136 | self._ttl = ttl | |
137 | ||
138 | self._socket.setsockopt( | |
139 | socket.IPPROTO_IPV6, | |
140 | socket.IPV6_MULTICAST_HOPS, | |
141 | ttl) | |
142 | ||
143 | @IPSocket.traffic_class.setter | |
144 | def traffic_class(self, traffic_class): | |
145 | self._traffic_class = traffic_class | |
146 | ||
147 | if not PLATFORM_WINDOWS: | |
148 | self._socket.setsockopt( | |
149 | socket.IPPROTO_IPV6, | |
150 | socket.IPV6_TCLASS, | |
151 | traffic_class) |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
24 | 26 | ''' |
25 | 27 | |
26 | 28 | from .exceptions import * |
27 | from .utils import is_ipv6_address | |
28 | 29 | |
29 | 30 | |
30 | 31 | class ICMPRequest: |
31 | 32 | ''' |
32 | A user-created object that represents an ICMP ECHO_REQUEST. | |
33 | A user-created object that represents an ICMP Echo Request. | |
33 | 34 | |
34 | 35 | :type destination: str |
35 | :param destination: The IP address of the gateway or host to which | |
36 | the message should be sent. | |
36 | :param destination: The IP address of the host to which the message | |
37 | should be sent. | |
37 | 38 | |
38 | 39 | :type id: int |
39 | 40 | :param id: The identifier of the request. Used to match the reply |
40 | 41 | with the request. In practice, a unique identifier is used for |
41 | every ping process. | |
42 | every ping process. On Linux, this identifier is automatically | |
43 | replaced if the request is sent from an unprivileged socket. | |
42 | 44 | |
43 | 45 | :type sequence: int |
44 | 46 | :param sequence: The sequence number. Used to match the reply with |
45 | 47 | the request. Typically, the sequence number is incremented for |
46 | 48 | each packet sent during the process. |
47 | 49 | |
48 | :type payload: bytes | |
49 | :param payload: (Optional) The payload content in bytes. A random | |
50 | payload is used by default. | |
51 | ||
52 | :type payload_size: int | |
53 | :param payload_size: (Optional) The payload size. Ignored when the | |
54 | `payload` parameter is set. | |
55 | ||
56 | :type timeout: int or float | |
57 | :param timeout: (Optional) The maximum waiting time for receiving | |
58 | the reply in seconds. | |
59 | ||
60 | :type ttl: int | |
61 | :param ttl: (Optional) The time to live of the packet in seconds. | |
62 | ||
63 | :type traffic_class: int | |
64 | :param traffic_class: (Optional) The traffic class of the packet. | |
65 | Provides a defined level of service to the packet by setting | |
66 | the DS Field (formerly TOS) or the Traffic Class field of the | |
67 | IP header. Packets are delivered with the minimum priority by | |
50 | :type payload: bytes, optional | |
51 | :param payload: The payload content in bytes. A random payload is | |
52 | used by default. | |
53 | ||
54 | :type payload_size: int, optional | |
55 | :param payload_size: The payload size. Ignored when the `payload` | |
56 | parameter is set. Default to 56. | |
57 | ||
58 | :type ttl: int, optional | |
59 | :param ttl: The time to live of the packet in terms of hops. | |
60 | Default to 64. | |
61 | ||
62 | :type traffic_class: int, optional | |
63 | :param traffic_class: The traffic class of the ICMP packet. | |
64 | Provides a defined level of service to the packet by setting the | |
65 | DS Field (formerly TOS) or the Traffic Class field of the IP | |
66 | header. Packets are delivered with the minimum priority by | |
68 | 67 | default (Best-effort delivery). |
69 | 68 | Intermediate routers must be able to support this feature. |
70 | 69 | Only available on Unix systems. Ignored on Windows. |
71 | 70 | |
72 | 71 | ''' |
72 | __slots__ = '_destination', '_id', '_sequence', '_payload', \ | |
73 | '_payload_size', '_ttl', '_traffic_class', '_time' | |
74 | ||
73 | 75 | def __init__(self, destination, id, sequence, payload=None, |
74 | payload_size=56, timeout=2, ttl=64, traffic_class=0): | |
75 | ||
76 | id &= 0xffff | |
77 | sequence &= 0xffff | |
76 | payload_size=56, ttl=64, traffic_class=0): | |
78 | 77 | |
79 | 78 | if payload: |
80 | 79 | payload_size = len(payload) |
81 | 80 | |
82 | 81 | self._destination = destination |
83 | self._id = id | |
84 | self._sequence = sequence | |
82 | self._id = id & 0xffff | |
83 | self._sequence = sequence & 0xffff | |
85 | 84 | self._payload = payload |
86 | 85 | self._payload_size = payload_size |
87 | self._timeout = timeout | |
88 | 86 | self._ttl = ttl |
89 | 87 | self._traffic_class = traffic_class |
90 | 88 | self._time = 0 |
95 | 93 | @property |
96 | 94 | def destination(self): |
97 | 95 | ''' |
98 | The IP address of the gateway or host to which the message | |
99 | should be sent. | |
96 | The IP address of the host to which the message should be sent. | |
100 | 97 | |
101 | 98 | ''' |
102 | 99 | return self._destination |
137 | 134 | return self._payload_size |
138 | 135 | |
139 | 136 | @property |
140 | def timeout(self): | |
141 | ''' | |
142 | The maximum waiting time for receiving the reply in seconds. | |
143 | ||
144 | ''' | |
145 | return self._timeout | |
146 | ||
147 | @property | |
148 | 137 | def ttl(self): |
149 | 138 | ''' |
150 | The time to live of the packet in seconds. | |
139 | The time to live of the packet in terms of hops. | |
151 | 140 | |
152 | 141 | ''' |
153 | 142 | return self._ttl |
164 | 153 | def time(self): |
165 | 154 | ''' |
166 | 155 | The timestamp of the ICMP request. |
156 | ||
167 | 157 | Initialized to zero when creating the request and replaced by |
168 | `ICMPv4Socket` or `ICMPv6Socket` with the time of sending. | |
158 | the `send` method of an ICMP socket with the time of sending. | |
169 | 159 | |
170 | 160 | ''' |
171 | 161 | return self._time |
173 | 163 | |
174 | 164 | class ICMPReply: |
175 | 165 | ''' |
176 | A class that represents an ICMP reply. Generated from an | |
177 | `ICMPSocket` object (`ICMPv4Socket` or `ICMPv6Socket`). | |
166 | A class that represents an ICMP reply. Generated from an ICMP socket. | |
178 | 167 | |
179 | 168 | :type source: str |
180 | :param source: The IP address of the gateway or host that composes | |
181 | the ICMP message. | |
169 | :param source: The IP address of the host that composes the ICMP | |
170 | message. | |
171 | ||
172 | :type family: int | |
173 | :param family: The address family. Can be set to `4` for IPv4 or `6` | |
174 | for IPv6 addresses. | |
182 | 175 | |
183 | 176 | :type id: int |
184 | :param id: The identifier of the reply. Used to match the reply | |
185 | with the request. | |
177 | :param id: The identifier of the reply. Used to match the reply with | |
178 | the request. | |
186 | 179 | |
187 | 180 | :type sequence: int |
188 | 181 | :param sequence: The sequence number. Used to match the reply with |
189 | 182 | the request. |
190 | 183 | |
191 | 184 | :type type: int |
192 | :param type: The type of message. | |
185 | :param type: The type of ICMP message. | |
193 | 186 | |
194 | 187 | :type code: int |
195 | :param code: The error code. | |
188 | :param code: The ICMP error code. | |
196 | 189 | |
197 | 190 | :type bytes_received: int |
198 | 191 | :param bytes_received: The number of bytes received. |
201 | 194 | :param time: The timestamp of the ICMP reply. |
202 | 195 | |
203 | 196 | ''' |
204 | def __init__(self, source, id, sequence, type, code, | |
197 | __slots__ = '_source', '_family', '_id', '_sequence', '_type', \ | |
198 | '_code', '_bytes_received', '_time' | |
199 | ||
200 | def __init__(self, source, family, id, sequence, type, code, | |
205 | 201 | bytes_received, time): |
206 | 202 | |
207 | 203 | self._source = source |
204 | self._family = family | |
208 | 205 | self._id = id |
209 | 206 | self._sequence = sequence |
210 | 207 | self._type = type |
217 | 214 | |
218 | 215 | def raise_for_status(self): |
219 | 216 | ''' |
220 | Throw an exception if the reply is not an ICMP ECHO_REPLY. | |
217 | Throw an exception if the reply is not an ICMP Echo Reply. | |
221 | 218 | Otherwise, do nothing. |
222 | 219 | |
223 | :raises ICMPv4DestinationUnreachable: If the ICMPv4 reply is | |
224 | type 3. | |
225 | :raises ICMPv4TimeExceeded: If the ICMPv4 reply is type 11. | |
226 | :raises ICMPv6DestinationUnreachable: If the ICMPv6 reply is | |
227 | type 1. | |
228 | :raises ICMPv6TimeExceeded: If the ICMPv6 reply is type 3. | |
229 | :raises ICMPError: If the reply is of another type and is not | |
230 | an ICMP ECHO_REPLY. | |
231 | ||
232 | ''' | |
233 | if is_ipv6_address(self._source): | |
234 | echo_reply_type = 129 | |
235 | errors = { | |
236 | 1: ICMPv6DestinationUnreachable, | |
237 | 3: ICMPv6TimeExceeded | |
238 | } | |
239 | ||
220 | :raises DestinationUnreachable: If the destination is | |
221 | unreachable for some reason. | |
222 | :raises TimeExceeded: If the time to live field of the ICMP | |
223 | request has reached zero. | |
224 | :raises ICMPError: Raised for any other type and ICMP error | |
225 | code, except ICMP Echo Reply messages. | |
226 | ||
227 | ''' | |
228 | if self._family == 6: | |
229 | if self._type == 1: | |
230 | raise ICMPv6DestinationUnreachable(self) | |
231 | ||
232 | if self._type == 3: | |
233 | raise ICMPv6TimeExceeded(self) | |
240 | 234 | else: |
241 | echo_reply_type = 0 | |
242 | errors = { | |
243 | 3: ICMPv4DestinationUnreachable, | |
244 | 11: ICMPv4TimeExceeded | |
245 | } | |
246 | ||
247 | if self._type in errors: | |
248 | raise errors[self._type](self) | |
249 | ||
250 | if self._type != echo_reply_type: | |
251 | message = f'Error type: {self._type}, ' \ | |
252 | f'code: {self._code}' | |
253 | ||
235 | if self._type == 3: | |
236 | raise ICMPv4DestinationUnreachable(self) | |
237 | ||
238 | if self._type == 11: | |
239 | raise ICMPv4TimeExceeded(self) | |
240 | ||
241 | if (self._family == 4 and self._type != 0 or | |
242 | self._family == 6 and self._type != 129): | |
243 | message = f'Error type: {self._type}, code: {self._code}' | |
254 | 244 | raise ICMPError(message, self) |
255 | 245 | |
256 | 246 | @property |
257 | 247 | def source(self): |
258 | 248 | ''' |
259 | The IP address of the gateway or host that composes the ICMP | |
260 | message. | |
249 | The IP address of the host that composes the ICMP message. | |
261 | 250 | |
262 | 251 | ''' |
263 | 252 | return self._source |
265 | 254 | @property |
266 | 255 | def id(self): |
267 | 256 | ''' |
268 | The identifier of the request. | |
257 | The identifier of the reply. | |
269 | 258 | Used to match the reply with the request. |
270 | 259 | |
271 | 260 | ''' |
283 | 272 | @property |
284 | 273 | def type(self): |
285 | 274 | ''' |
286 | The type of message. | |
275 | The type of ICMP message. | |
287 | 276 | |
288 | 277 | ''' |
289 | 278 | return self._type |
291 | 280 | @property |
292 | 281 | def code(self): |
293 | 282 | ''' |
294 | The error code. | |
283 | The ICMP error code. | |
295 | 284 | |
296 | 285 | ''' |
297 | 286 | return self._code |
305 | 294 | return self._bytes_received |
306 | 295 | |
307 | 296 | @property |
308 | def received_bytes(self): | |
309 | ''' | |
310 | Deprecated: use the `bytes_received` property instead. | |
311 | ||
312 | ''' | |
313 | print('[icmplib] Deprecation Warning: The `received_bytes` ' | |
314 | 'property will be removed from icmplib 2.0. Use the ' | |
315 | '`bytes_received` property instead.') | |
316 | ||
317 | return self._bytes_received | |
318 | ||
319 | @property | |
320 | 297 | def time(self): |
321 | 298 | ''' |
322 | 299 | The timestamp of the ICMP reply. |
327 | 304 | |
328 | 305 | class Host: |
329 | 306 | ''' |
330 | A class that represents a host. Simplifies the exploitation of | |
331 | results from `ping` and `traceroute` functions. | |
307 | A class that represents a host. It simplifies the use of the results | |
308 | from the `ping`, `multiping` and `traceroute` functions. | |
332 | 309 | |
333 | 310 | :type address: str |
334 | :param address: The IP address of the gateway or host that | |
335 | responded to the request. | |
336 | ||
337 | :type min_rtt: float | |
338 | :param min_rtt: The minimum round-trip time. | |
339 | ||
340 | :type avg_rtt: float | |
341 | :param avg_rtt: The average round-trip time. | |
342 | ||
343 | :type max_rtt: float | |
344 | :param max_rtt: The maximum round-trip time. | |
311 | :param address: The IP address of the host that responded to the | |
312 | request. | |
345 | 313 | |
346 | 314 | :type packets_sent: int |
347 | 315 | :param packets_sent: The number of packets transmitted to the |
348 | 316 | destination host. |
349 | 317 | |
350 | :type packets_received: int | |
351 | :param packets_received: The number of packets sent by the remote | |
352 | host and received by the current host. | |
353 | ||
354 | ''' | |
355 | def __init__(self, address, min_rtt, avg_rtt, max_rtt, | |
356 | packets_sent, packets_received): | |
357 | ||
318 | :type rtts: list[float] | |
319 | :param rtts: The list of round-trip times expressed in milliseconds. | |
320 | ||
321 | ''' | |
322 | __slots__ = '_address', '_packets_sent', '_rtts' | |
323 | ||
324 | def __init__(self, address, packets_sent, rtts): | |
358 | 325 | self._address = address |
359 | self._min_rtt = round(min_rtt, 3) | |
360 | self._avg_rtt = round(avg_rtt, 3) | |
361 | self._max_rtt = round(max_rtt, 3) | |
362 | 326 | self._packets_sent = packets_sent |
363 | self._packets_received = packets_received | |
327 | self._rtts = rtts | |
364 | 328 | |
365 | 329 | def __repr__(self): |
366 | 330 | return f'<Host [{self._address}]>' |
367 | 331 | |
332 | def __str__(self): | |
333 | return f' {self._address}\n' + '-' * 60 + '\n' \ | |
334 | f' Packets sent: {self._packets_sent}\n' \ | |
335 | f' Packets received: {self.packets_received}\n' \ | |
336 | f' Packet loss: {self.packet_loss * 100}%\n' \ | |
337 | f' Round-trip times: {self.min_rtt} ms / ' \ | |
338 | f'{self.avg_rtt} ms / {self.max_rtt} ms\n' \ | |
339 | f' Jitter: {self.jitter} ms\n' + '-' * 60 | |
340 | ||
368 | 341 | @property |
369 | 342 | def address(self): |
370 | 343 | ''' |
371 | The IP address of the gateway or host that responded to the | |
372 | request. | |
344 | The IP address of the host that responded to the request. | |
373 | 345 | |
374 | 346 | ''' |
375 | 347 | return self._address |
377 | 349 | @property |
378 | 350 | def min_rtt(self): |
379 | 351 | ''' |
380 | The minimum round-trip time. | |
381 | ||
382 | ''' | |
383 | return self._min_rtt | |
352 | The minimum round-trip time in milliseconds. | |
353 | ||
354 | ''' | |
355 | if not self._rtts: | |
356 | return 0.0 | |
357 | ||
358 | return round(min(self._rtts), 3) | |
384 | 359 | |
385 | 360 | @property |
386 | 361 | def avg_rtt(self): |
387 | 362 | ''' |
388 | The average round-trip time. | |
389 | ||
390 | ''' | |
391 | return self._avg_rtt | |
363 | The average round-trip time in milliseconds. | |
364 | ||
365 | ''' | |
366 | if not self._rtts: | |
367 | return 0.0 | |
368 | ||
369 | return round(sum(self._rtts) / len(self._rtts), 3) | |
392 | 370 | |
393 | 371 | @property |
394 | 372 | def max_rtt(self): |
395 | 373 | ''' |
396 | The maximum round-trip time. | |
397 | ||
398 | ''' | |
399 | return self._max_rtt | |
374 | The maximum round-trip time in milliseconds. | |
375 | ||
376 | ''' | |
377 | if not self._rtts: | |
378 | return 0.0 | |
379 | ||
380 | return round(max(self._rtts), 3) | |
381 | ||
382 | @property | |
383 | def rtts(self): | |
384 | ''' | |
385 | The list of round-trip times expressed in milliseconds. | |
386 | ||
387 | ''' | |
388 | return self._rtts | |
400 | 389 | |
401 | 390 | @property |
402 | 391 | def packets_sent(self): |
403 | 392 | ''' |
404 | The number of packets transmitted to the destination host. | |
393 | The number of requests transmitted to the remote host. | |
405 | 394 | |
406 | 395 | ''' |
407 | 396 | return self._packets_sent |
408 | 397 | |
409 | 398 | @property |
410 | def transmitted_packets(self): | |
411 | ''' | |
412 | Deprecated: use the `packets_sent` property instead. | |
413 | ||
414 | ''' | |
415 | print('[icmplib] Deprecation Warning: The ' | |
416 | '`transmitted_packets` property will be removed from ' | |
417 | 'icmplib 2.0. Use the `packets_sent` property instead.') | |
418 | ||
419 | return self._packets_sent | |
420 | ||
421 | @property | |
422 | 399 | def packets_received(self): |
423 | 400 | ''' |
424 | The number of packets sent by the remote host and received by | |
425 | the current host. | |
426 | ||
427 | ''' | |
428 | return self._packets_received | |
429 | ||
430 | @property | |
431 | def received_packets(self): | |
432 | ''' | |
433 | Deprecated: use the `packets_received` property instead. | |
434 | ||
435 | ''' | |
436 | print('[icmplib] Deprecation Warning: The `received_packets` ' | |
437 | 'property will be removed from icmplib 2.0. Use the ' | |
438 | '`packets_received` property instead.') | |
439 | ||
440 | return self._packets_received | |
401 | The number of ICMP responses received from the remote host. | |
402 | ||
403 | ''' | |
404 | return len(self._rtts) | |
441 | 405 | |
442 | 406 | @property |
443 | 407 | def packet_loss(self): |
449 | 413 | if not self._packets_sent: |
450 | 414 | return 0.0 |
451 | 415 | |
452 | return 1 - self._packets_received / self._packets_sent | |
416 | return round(1 - len(self._rtts) / self._packets_sent, 2) | |
417 | ||
418 | @property | |
419 | def jitter(self): | |
420 | ''' | |
421 | The jitter in milliseconds, defined as the variance of the | |
422 | latency of packets flowing through the network. | |
423 | ||
424 | At least two ICMP responses are required to calculate the | |
425 | jitter. | |
426 | ||
427 | ''' | |
428 | sum_deltas = 0.0 | |
429 | num_deltas = len(self._rtts) - 1 | |
430 | ||
431 | if num_deltas < 1: | |
432 | return 0.0 | |
433 | ||
434 | for i in range(num_deltas): | |
435 | sum_deltas += abs(self._rtts[i] - self._rtts[i + 1]) | |
436 | ||
437 | return round(sum_deltas / num_deltas, 3) | |
453 | 438 | |
454 | 439 | @property |
455 | 440 | def is_alive(self): |
456 | 441 | ''' |
457 | Indicate whether the host is reachable. Return a `boolean`. | |
458 | ||
459 | ''' | |
460 | return self._packets_received > 0 | |
442 | Indicate whether the host is reachable. | |
443 | Return a `boolean`. | |
444 | ||
445 | ''' | |
446 | return len(self._rtts) > 0 | |
461 | 447 | |
462 | 448 | |
463 | 449 | class Hop(Host): |
466 | 452 | some features for the `traceroute` function. |
467 | 453 | |
468 | 454 | :type address: str |
469 | :param address: The IP address of the gateway or host that | |
470 | responded to the request. | |
471 | ||
472 | :type min_rtt: float | |
473 | :param min_rtt: The minimum round-trip time. | |
474 | ||
475 | :type avg_rtt: float | |
476 | :param avg_rtt: The average round-trip time. | |
477 | ||
478 | :type max_rtt: float | |
479 | :param max_rtt: The maximum round-trip time. | |
455 | :param address: The IP address of the gateway or host that responded | |
456 | to the request. | |
480 | 457 | |
481 | 458 | :type packets_sent: int |
482 | 459 | :param packets_sent: The number of packets transmitted to the |
483 | 460 | destination host. |
484 | 461 | |
485 | :type packets_received: int | |
486 | :param packets_received: The number of packets sent by the remote | |
487 | host and received by the current host. | |
462 | :type rtts: list[float] | |
463 | :param rtts: The list of round-trip times expressed in milliseconds. | |
488 | 464 | |
489 | 465 | :type distance: int |
490 | :param distance: The distance (in terms of hops) that separates the | |
466 | :param distance: The distance, in terms of hops, that separates the | |
491 | 467 | remote host from the current machine. |
492 | 468 | |
493 | 469 | ''' |
494 | def __init__(self, address, min_rtt, avg_rtt, max_rtt, | |
495 | packets_sent, packets_received, distance): | |
496 | ||
497 | super().__init__(address, min_rtt, avg_rtt, max_rtt, | |
498 | packets_sent, packets_received) | |
499 | ||
470 | __slots__ = '_address', '_packets_sent', '_rtts', '_distance' | |
471 | ||
472 | def __init__(self, address, packets_sent, rtts, distance): | |
473 | super().__init__(address, packets_sent, rtts) | |
500 | 474 | self._distance = distance |
501 | 475 | |
502 | 476 | def __repr__(self): |
503 | 477 | return f'<Hop {self._distance} [{self._address}]>' |
504 | 478 | |
479 | def __str__(self): | |
480 | return f' #{self._distance:<2} {super().__str__()[2:]}' | |
481 | ||
505 | 482 | @property |
506 | 483 | def distance(self): |
507 | 484 | ''' |
508 | The distance (in terms of hops) that separates the remote host | |
485 | The distance, in terms of hops, that separates the remote host | |
509 | 486 | from the current machine. |
510 | 487 | |
511 | 488 | ''' |
0 | ''' | |
1 | icmplib | |
2 | ~~~~~~~ | |
3 | ||
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
6 | https://github.com/ValentinBELYN/icmplib | |
7 | ||
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
9 | :license: GNU LGPLv3, see the LICENSE for details. | |
10 | ||
11 | ~~~~~~~ | |
12 | ||
13 | This program is free software: you can redistribute it and/or | |
14 | modify it under the terms of the GNU Lesser General Public License | |
15 | as published by the Free Software Foundation, either version 3 of | |
16 | the License, or (at your option) any later version. | |
17 | ||
18 | This program is distributed in the hope that it will be useful, | |
19 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | GNU Lesser General Public License for more details. | |
22 | ||
23 | You should have received a copy of the GNU Lesser General Public | |
24 | License along with this program. If not, see | |
25 | <https://www.gnu.org/licenses/>. | |
26 | ''' | |
27 | ||
28 | import asyncio | |
29 | ||
30 | from .ping import async_ping | |
31 | ||
32 | ||
33 | async def async_multiping(addresses, count=2, interval=0.5, timeout=2, | |
34 | concurrent_tasks=50, source=None, family=None, privileged=True, | |
35 | **kwargs): | |
36 | ''' | |
37 | Send ICMP Echo Request packets to several network hosts. | |
38 | ||
39 | This function is non-blocking. | |
40 | ||
41 | :type addresses: list[str] | |
42 | :param addresses: The IP addresses of the hosts to which messages | |
43 | should be sent. Hostnames and FQDNs are allowed but not | |
44 | recommended. You can easily retrieve their IP address by calling | |
45 | the built-in `async_resolve` function. | |
46 | ||
47 | :type count: int, optional | |
48 | :param count: The number of ping to perform per address. | |
49 | Default to 2. | |
50 | ||
51 | :type interval: int or float, optional | |
52 | :param interval: The interval in seconds between sending each packet. | |
53 | Default to 0.5. | |
54 | ||
55 | :type timeout: int or float, optional | |
56 | :param timeout: The maximum waiting time for receiving a reply in | |
57 | seconds. Default to 2. | |
58 | ||
59 | :type concurrent_tasks: int, optional | |
60 | :param concurrent_tasks: The maximum number of concurrent tasks to | |
61 | speed up processing. This value cannot exceed the maximum number | |
62 | of file descriptors configured on the operating system. | |
63 | Default to 50. | |
64 | ||
65 | :type source: str, optional | |
66 | :param source: The IP address from which you want to send packets. | |
67 | By default, the interface is automatically chosen according to | |
68 | the specified destinations. This parameter should not be used if | |
69 | you are passing both IPv4 and IPv6 addresses to this function. | |
70 | ||
71 | :type family: int, optional | |
72 | :param family: The address family if a hostname or FQDN is specified. | |
73 | Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, | |
74 | this function searches for IPv4 addresses first before searching | |
75 | for IPv6 addresses. | |
76 | ||
77 | :type privileged: bool, optional | |
78 | :param privileged: When this option is enabled, this library fully | |
79 | manages the exchanges and the structure of ICMP packets. | |
80 | Disable this option if you want to use this function without | |
81 | root privileges and let the kernel handle ICMP headers. | |
82 | Default to True. | |
83 | Only available on Unix systems. Ignored on Windows. | |
84 | ||
85 | Advanced (**kwags): | |
86 | ||
87 | :type payload: bytes, optional | |
88 | :param payload: The payload content in bytes. A random payload is | |
89 | used by default. | |
90 | ||
91 | :type payload_size: int, optional | |
92 | :param payload_size: The payload size. Ignored when the `payload` | |
93 | parameter is set. Default to 56. | |
94 | ||
95 | :type traffic_class: int, optional | |
96 | :param traffic_class: The traffic class of ICMP packets. | |
97 | Provides a defined level of service to packets by setting the DS | |
98 | Field (formerly TOS) or the Traffic Class field of IP headers. | |
99 | Packets are delivered with the minimum priority by default | |
100 | (Best-effort delivery). | |
101 | Intermediate routers must be able to support this feature. | |
102 | Only available on Unix systems. Ignored on Windows. | |
103 | ||
104 | :rtype: list[Host] | |
105 | :returns: A list of `Host` objects containing statistics about the | |
106 | desired destinations. The list is sorted in the same order as | |
107 | the addresses passed in parameters. | |
108 | ||
109 | :raises NameLookupError: If you pass a hostname or FQDN in | |
110 | parameters and it does not exist or cannot be resolved. | |
111 | :raises SocketPermissionError: If the privileges are insufficient to | |
112 | create the socket. | |
113 | :raises SocketAddressError: If the source address cannot be assigned | |
114 | to the socket. | |
115 | :raises ICMPSocketError: If another error occurs. See the | |
116 | `ICMPv4Socket` or `ICMPv6Socket` class for details. | |
117 | ||
118 | Usage:: | |
119 | ||
120 | >>> import asyncio | |
121 | >>> from icmplib import async_multiping | |
122 | >>> hosts = asyncio.run(async_multiping(['10.0.0.5', '::1'])) | |
123 | ||
124 | >>> for host in hosts: | |
125 | ... if host.is_alive: | |
126 | ... print(f'{host.address} is up!') | |
127 | ... else: | |
128 | ... print(f'{host.address} is down!') | |
129 | ||
130 | 10.0.0.5 is down! | |
131 | ::1 is up! | |
132 | ||
133 | See the `Host` class for details. | |
134 | ||
135 | ''' | |
136 | loop = asyncio.get_running_loop() | |
137 | tasks = [] | |
138 | tasks_pending = set() | |
139 | ||
140 | for address in addresses: | |
141 | if len(tasks_pending) >= concurrent_tasks: | |
142 | _, tasks_pending = await asyncio.wait( | |
143 | tasks_pending, | |
144 | return_when=asyncio.FIRST_COMPLETED) | |
145 | ||
146 | task = loop.create_task( | |
147 | async_ping( | |
148 | address=address, | |
149 | count=count, | |
150 | interval=interval, | |
151 | timeout=timeout, | |
152 | source=source, | |
153 | family=family, | |
154 | privileged=privileged, | |
155 | **kwargs)) | |
156 | ||
157 | tasks.append(task) | |
158 | tasks_pending.add(task) | |
159 | ||
160 | await asyncio.wait(tasks_pending) | |
161 | ||
162 | return [task.result() for task in tasks] | |
163 | ||
164 | ||
165 | def multiping(addresses, count=2, interval=0.5, timeout=2, | |
166 | concurrent_tasks=50, source=None, family=None, privileged=True, | |
167 | **kwargs): | |
168 | ''' | |
169 | Send ICMP Echo Request packets to several network hosts. | |
170 | ||
171 | :type addresses: list[str] | |
172 | :param addresses: The IP addresses of the hosts to which messages | |
173 | should be sent. Hostnames and FQDNs are allowed but not | |
174 | recommended. You can easily retrieve their IP address by calling | |
175 | the built-in `resolve` function. | |
176 | ||
177 | :type count: int, optional | |
178 | :param count: The number of ping to perform per address. | |
179 | Default to 2. | |
180 | ||
181 | :type interval: int or float, optional | |
182 | :param interval: The interval in seconds between sending each packet. | |
183 | Default to 0.5. | |
184 | ||
185 | :type timeout: int or float, optional | |
186 | :param timeout: The maximum waiting time for receiving a reply in | |
187 | seconds. Default to 2. | |
188 | ||
189 | :type concurrent_tasks: int, optional | |
190 | :param concurrent_tasks: The maximum number of concurrent tasks to | |
191 | speed up processing. This value cannot exceed the maximum number | |
192 | of file descriptors configured on the operating system. | |
193 | Default to 50. | |
194 | ||
195 | :type source: str, optional | |
196 | :param source: The IP address from which you want to send packets. | |
197 | By default, the interface is automatically chosen according to | |
198 | the specified destinations. This parameter should not be used if | |
199 | you are passing both IPv4 and IPv6 addresses to this function. | |
200 | ||
201 | :type family: int, optional | |
202 | :param family: The address family if a hostname or FQDN is specified. | |
203 | Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, | |
204 | this function searches for IPv4 addresses first before searching | |
205 | for IPv6 addresses. | |
206 | ||
207 | :type privileged: bool, optional | |
208 | :param privileged: When this option is enabled, this library fully | |
209 | manages the exchanges and the structure of ICMP packets. | |
210 | Disable this option if you want to use this function without | |
211 | root privileges and let the kernel handle ICMP headers. | |
212 | Default to True. | |
213 | Only available on Unix systems. Ignored on Windows. | |
214 | ||
215 | Advanced (**kwags): | |
216 | ||
217 | :type payload: bytes, optional | |
218 | :param payload: The payload content in bytes. A random payload is | |
219 | used by default. | |
220 | ||
221 | :type payload_size: int, optional | |
222 | :param payload_size: The payload size. Ignored when the `payload` | |
223 | parameter is set. Default to 56. | |
224 | ||
225 | :type traffic_class: int, optional | |
226 | :param traffic_class: The traffic class of ICMP packets. | |
227 | Provides a defined level of service to packets by setting the DS | |
228 | Field (formerly TOS) or the Traffic Class field of IP headers. | |
229 | Packets are delivered with the minimum priority by default | |
230 | (Best-effort delivery). | |
231 | Intermediate routers must be able to support this feature. | |
232 | Only available on Unix systems. Ignored on Windows. | |
233 | ||
234 | :rtype: list[Host] | |
235 | :returns: A list of `Host` objects containing statistics about the | |
236 | desired destinations. The list is sorted in the same order as | |
237 | the addresses passed in parameters. | |
238 | ||
239 | :raises NameLookupError: If you pass a hostname or FQDN in | |
240 | parameters and it does not exist or cannot be resolved. | |
241 | :raises SocketPermissionError: If the privileges are insufficient to | |
242 | create the socket. | |
243 | :raises SocketAddressError: If the source address cannot be assigned | |
244 | to the socket. | |
245 | :raises ICMPSocketError: If another error occurs. See the | |
246 | `ICMPv4Socket` or `ICMPv6Socket` class for details. | |
247 | ||
248 | Usage:: | |
249 | ||
250 | >>> from icmplib import multiping | |
251 | >>> hosts = multiping(['10.0.0.5', '127.0.0.1', '::1']) | |
252 | ||
253 | >>> for host in hosts: | |
254 | ... if host.is_alive: | |
255 | ... print(f'{host.address} is up!') | |
256 | ... else: | |
257 | ... print(f'{host.address} is down!') | |
258 | ||
259 | 10.0.0.5 is down! | |
260 | 127.0.0.1 is up! | |
261 | ::1 is up! | |
262 | ||
263 | See the `Host` class for details. | |
264 | ||
265 | ''' | |
266 | return asyncio.run( | |
267 | async_multiping( | |
268 | addresses=addresses, | |
269 | count=count, | |
270 | interval=interval, | |
271 | timeout=timeout, | |
272 | concurrent_tasks=concurrent_tasks, | |
273 | source=source, | |
274 | family=family, | |
275 | privileged=privileged, | |
276 | **kwargs)) |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
23 | 25 | <https://www.gnu.org/licenses/>. |
24 | 26 | ''' |
25 | 27 | |
26 | from threading import Thread | |
28 | import asyncio | |
27 | 29 | from time import sleep |
28 | 30 | |
29 | from .sockets import ICMPv4Socket, ICMPv6Socket | |
31 | from .sockets import ICMPv4Socket, ICMPv6Socket, AsyncSocket | |
30 | 32 | from .models import ICMPRequest, Host |
31 | 33 | from .exceptions import * |
32 | from .utils import PID, resolve, is_ipv6_address | |
33 | ||
34 | ||
35 | class PingThread(Thread): | |
36 | def __init__(self, **kwargs): | |
37 | super().__init__() | |
38 | self._kwargs = kwargs | |
39 | self._host = None | |
40 | ||
41 | def run(self): | |
42 | self._host = ping(**self._kwargs) | |
43 | ||
44 | @property | |
45 | def host(self): | |
46 | return self._host | |
47 | ||
48 | ||
49 | def ping(address, count=4, interval=1, timeout=2, id=PID, **kwargs): | |
50 | ''' | |
51 | Send ICMP ECHO_REQUEST packets to a network host. | |
34 | from .utils import * | |
35 | ||
36 | ||
37 | def ping(address, count=4, interval=1, timeout=2, id=None, source=None, | |
38 | family=None, privileged=True, **kwargs): | |
39 | ''' | |
40 | Send ICMP Echo Request packets to a network host. | |
52 | 41 | |
53 | 42 | :type address: str |
54 | :param address: The IP address of the gateway or host to which | |
55 | the message should be sent. | |
56 | ||
57 | :type count: int | |
58 | :param count: (Optional) The number of ping to perform. | |
59 | ||
60 | :type interval: int or float | |
61 | :param interval: (Optional) The interval in seconds between sending | |
62 | each packet. | |
63 | ||
64 | :type timeout: int or float | |
65 | :param timeout: (Optional) The maximum waiting time for receiving | |
66 | a reply in seconds. | |
67 | ||
68 | :type id: int | |
69 | :param id: (Optional) The identifier of the request. Used to match | |
70 | the reply with the request. In practice, a unique identifier is | |
71 | used for every ping process. | |
72 | ||
73 | :param **kwargs: (Optional) Advanced use: arguments passed to | |
74 | `ICMPRequest` objects. | |
43 | :param address: The IP address, hostname or FQDN of the host to | |
44 | which messages should be sent. For deterministic behavior, | |
45 | prefer to use an IP address. | |
46 | ||
47 | :type count: int, optional | |
48 | :param count: The number of ping to perform. Default to 4. | |
49 | ||
50 | :type interval: int or float, optional | |
51 | :param interval: The interval in seconds between sending each packet. | |
52 | Default to 1. | |
53 | ||
54 | :type timeout: int or float, optional | |
55 | :param timeout: The maximum waiting time for receiving a reply in | |
56 | seconds. Default to 2. | |
57 | ||
58 | :type id: int, optional | |
59 | :param id: The identifier of ICMP requests. Used to match the | |
60 | responses with requests. In practice, a unique identifier should | |
61 | be used for every ping process. On Linux, this identifier is | |
62 | ignored when the `privileged` parameter is disabled. The library | |
63 | handles this identifier itself by default. | |
64 | ||
65 | :type source: str, optional | |
66 | :param source: The IP address from which you want to send packets. | |
67 | By default, the interface is automatically chosen according to | |
68 | the specified destination. | |
69 | ||
70 | :type family: int, optional | |
71 | :param family: The address family if a hostname or FQDN is specified. | |
72 | Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, | |
73 | this function searches for IPv4 addresses first before searching | |
74 | for IPv6 addresses. | |
75 | ||
76 | :type privileged: bool, optional | |
77 | :param privileged: When this option is enabled, this library fully | |
78 | manages the exchanges and the structure of ICMP packets. | |
79 | Disable this option if you want to use this function without | |
80 | root privileges and let the kernel handle ICMP headers. | |
81 | Default to True. | |
82 | Only available on Unix systems. Ignored on Windows. | |
83 | ||
84 | Advanced (**kwags): | |
85 | ||
86 | :type payload: bytes, optional | |
87 | :param payload: The payload content in bytes. A random payload is | |
88 | used by default. | |
89 | ||
90 | :type payload_size: int, optional | |
91 | :param payload_size: The payload size. Ignored when the `payload` | |
92 | parameter is set. Default to 56. | |
93 | ||
94 | :type traffic_class: int, optional | |
95 | :param traffic_class: The traffic class of ICMP packets. | |
96 | Provides a defined level of service to packets by setting the DS | |
97 | Field (formerly TOS) or the Traffic Class field of IP headers. | |
98 | Packets are delivered with the minimum priority by default | |
99 | (Best-effort delivery). | |
100 | Intermediate routers must be able to support this feature. | |
101 | Only available on Unix systems. Ignored on Windows. | |
75 | 102 | |
76 | 103 | :rtype: Host |
77 | 104 | :returns: A `Host` object containing statistics about the desired |
78 | 105 | destination. |
79 | 106 | |
80 | :raises SocketPermissionError: If the permissions are insufficient | |
81 | to create a socket. | |
107 | :raises NameLookupError: If you pass a hostname or FQDN in | |
108 | parameters and it does not exist or cannot be resolved. | |
109 | :raises SocketPermissionError: If the privileges are insufficient to | |
110 | create the socket. | |
111 | :raises SocketAddressError: If the source address cannot be assigned | |
112 | to the socket. | |
113 | :raises ICMPSocketError: If another error occurs. See the | |
114 | `ICMPv4Socket` or `ICMPv6Socket` class for details. | |
82 | 115 | |
83 | 116 | Usage:: |
84 | 117 | |
85 | 118 | >>> from icmplib import ping |
86 | 119 | >>> host = ping('1.1.1.1') |
87 | ||
88 | 120 | >>> host.avg_rtt |
89 | 121 | 13.2 |
90 | ||
91 | 122 | >>> host.is_alive |
92 | 123 | True |
93 | 124 | |
94 | 125 | See the `Host` class for details. |
95 | 126 | |
96 | 127 | ''' |
97 | address = resolve(address) | |
128 | if is_hostname(address): | |
129 | address = resolve(address, family)[0] | |
98 | 130 | |
99 | 131 | if is_ipv6_address(address): |
100 | socket = ICMPv6Socket() | |
101 | ||
132 | _Socket = ICMPv6Socket | |
102 | 133 | else: |
103 | socket = ICMPv4Socket() | |
104 | ||
134 | _Socket = ICMPv4Socket | |
135 | ||
136 | id = id or unique_identifier() | |
105 | 137 | packets_sent = 0 |
106 | packets_received = 0 | |
107 | ||
108 | min_rtt = float('inf') | |
109 | avg_rtt = 0.0 | |
110 | max_rtt = 0.0 | |
111 | ||
112 | for sequence in range(count): | |
113 | request = ICMPRequest( | |
114 | destination=address, | |
115 | id=id, | |
116 | sequence=sequence, | |
117 | timeout=timeout, | |
118 | **kwargs) | |
119 | ||
120 | try: | |
121 | socket.send(request) | |
122 | packets_sent += 1 | |
123 | ||
124 | reply = socket.receive() | |
125 | reply.raise_for_status() | |
126 | packets_received += 1 | |
127 | ||
128 | round_trip_time = (reply.time - request.time) * 1000 | |
129 | avg_rtt += round_trip_time | |
130 | min_rtt = min(round_trip_time, min_rtt) | |
131 | max_rtt = max(round_trip_time, max_rtt) | |
132 | ||
133 | if sequence < count - 1: | |
138 | rtts = [] | |
139 | ||
140 | with _Socket(source, privileged) as sock: | |
141 | for sequence in range(count): | |
142 | if sequence > 0: | |
134 | 143 | sleep(interval) |
135 | 144 | |
136 | except ICMPLibError: | |
137 | pass | |
138 | ||
139 | if packets_received: | |
140 | avg_rtt /= packets_received | |
141 | ||
145 | request = ICMPRequest( | |
146 | destination=address, | |
147 | id=id, | |
148 | sequence=sequence, | |
149 | **kwargs) | |
150 | ||
151 | try: | |
152 | sock.send(request) | |
153 | packets_sent += 1 | |
154 | ||
155 | reply = sock.receive(request, timeout) | |
156 | reply.raise_for_status() | |
157 | ||
158 | rtt = (reply.time - request.time) * 1000 | |
159 | rtts.append(rtt) | |
160 | ||
161 | except ICMPLibError: | |
162 | pass | |
163 | ||
164 | return Host(address, packets_sent, rtts) | |
165 | ||
166 | ||
167 | async def async_ping(address, count=4, interval=1, timeout=2, id=None, | |
168 | source=None, family=None, privileged=True, **kwargs): | |
169 | ''' | |
170 | Send ICMP Echo Request packets to a network host. | |
171 | ||
172 | This function is non-blocking. | |
173 | ||
174 | :type address: str | |
175 | :param address: The IP address, hostname or FQDN of the host to | |
176 | which messages should be sent. For deterministic behavior, | |
177 | prefer to use an IP address. | |
178 | ||
179 | :type count: int, optional | |
180 | :param count: The number of ping to perform. Default to 4. | |
181 | ||
182 | :type interval: int or float, optional | |
183 | :param interval: The interval in seconds between sending each packet. | |
184 | Default to 1. | |
185 | ||
186 | :type timeout: int or float, optional | |
187 | :param timeout: The maximum waiting time for receiving a reply in | |
188 | seconds. Default to 2. | |
189 | ||
190 | :type id: int, optional | |
191 | :param id: The identifier of ICMP requests. Used to match the | |
192 | responses with requests. In practice, a unique identifier should | |
193 | be used for every ping process. On Linux, this identifier is | |
194 | ignored when the `privileged` parameter is disabled. The library | |
195 | handles this identifier itself by default. | |
196 | ||
197 | :type source: str, optional | |
198 | :param source: The IP address from which you want to send packets. | |
199 | By default, the interface is automatically chosen according to | |
200 | the specified destination. | |
201 | ||
202 | :type family: int, optional | |
203 | :param family: The address family if a hostname or FQDN is specified. | |
204 | Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, | |
205 | this function searches for IPv4 addresses first before searching | |
206 | for IPv6 addresses. | |
207 | ||
208 | :type privileged: bool, optional | |
209 | :param privileged: When this option is enabled, this library fully | |
210 | manages the exchanges and the structure of ICMP packets. | |
211 | Disable this option if you want to use this function without | |
212 | root privileges and let the kernel handle ICMP headers. | |
213 | Default to True. | |
214 | Only available on Unix systems. Ignored on Windows. | |
215 | ||
216 | Advanced (**kwags): | |
217 | ||
218 | :type payload: bytes, optional | |
219 | :param payload: The payload content in bytes. A random payload is | |
220 | used by default. | |
221 | ||
222 | :type payload_size: int, optional | |
223 | :param payload_size: The payload size. Ignored when the `payload` | |
224 | parameter is set. Default to 56. | |
225 | ||
226 | :type traffic_class: int, optional | |
227 | :param traffic_class: The traffic class of ICMP packets. | |
228 | Provides a defined level of service to packets by setting the DS | |
229 | Field (formerly TOS) or the Traffic Class field of IP headers. | |
230 | Packets are delivered with the minimum priority by default | |
231 | (Best-effort delivery). | |
232 | Intermediate routers must be able to support this feature. | |
233 | Only available on Unix systems. Ignored on Windows. | |
234 | ||
235 | :rtype: Host | |
236 | :returns: A `Host` object containing statistics about the desired | |
237 | destination. | |
238 | ||
239 | :raises NameLookupError: If you pass a hostname or FQDN in | |
240 | parameters and it does not exist or cannot be resolved. | |
241 | :raises SocketPermissionError: If the privileges are insufficient to | |
242 | create the socket. | |
243 | :raises SocketAddressError: If the source address cannot be assigned | |
244 | to the socket. | |
245 | :raises ICMPSocketError: If another error occurs. See the | |
246 | `ICMPv4Socket` or `ICMPv6Socket` class for details. | |
247 | ||
248 | Usage:: | |
249 | ||
250 | >>> import asyncio | |
251 | >>> from icmplib import async_ping | |
252 | >>> host = asyncio.run(async_ping('1.1.1.1')) | |
253 | >>> host.avg_rtt | |
254 | 13.2 | |
255 | >>> host.is_alive | |
256 | True | |
257 | ||
258 | See the `Host` class for details. | |
259 | ||
260 | ''' | |
261 | if is_hostname(address): | |
262 | address = (await async_resolve(address, family))[0] | |
263 | ||
264 | if is_ipv6_address(address): | |
265 | _Socket = ICMPv6Socket | |
142 | 266 | else: |
143 | min_rtt = 0.0 | |
144 | ||
145 | host = Host( | |
146 | address=address, | |
147 | min_rtt=min_rtt, | |
148 | avg_rtt=avg_rtt, | |
149 | max_rtt=max_rtt, | |
150 | packets_sent=packets_sent, | |
151 | packets_received=packets_received) | |
152 | ||
153 | socket.close() | |
154 | ||
155 | return host | |
156 | ||
157 | ||
158 | def multiping(addresses, count=2, interval=1, timeout=2, id=PID, | |
159 | max_threads=10, **kwargs): | |
160 | ''' | |
161 | Send ICMP ECHO_REQUEST packets to multiple network hosts. | |
162 | ||
163 | :type addresses: list of str | |
164 | :param addresses: The IP addresses of the gateways or hosts to | |
165 | which messages should be sent. | |
166 | ||
167 | :type count: int | |
168 | :param count: (Optional) The number of ping to perform per address. | |
169 | ||
170 | :type interval: int or float | |
171 | :param interval: (Optional) The interval in seconds between sending | |
172 | each packet. | |
173 | ||
174 | :type timeout: int or float | |
175 | :param timeout: (Optional) The maximum waiting time for receiving | |
176 | a reply in seconds. | |
177 | ||
178 | :type id: int | |
179 | :param id: (Optional) The identifier of the requests. This | |
180 | identifier will be incremented by one for each destination. | |
181 | ||
182 | :type max_threads: int | |
183 | :param max_threads: (Optional) The number of threads allowed to | |
184 | speed up processing. | |
185 | ||
186 | :param **kwargs: (Optional) Advanced use: arguments passed to | |
187 | `ICMPRequest` objects. | |
188 | ||
189 | :rtype: list of Host | |
190 | :returns: A list of `Host` objects containing statistics about the | |
191 | desired destinations. The list is sorted in the same order as | |
192 | the addresses passed in parameters. | |
193 | ||
194 | :raises SocketPermissionError: If the permissions are insufficient | |
195 | to create a socket. | |
196 | ||
197 | Usage:: | |
198 | ||
199 | >>> from icmplib import multiping | |
200 | >>> hosts = multiping(['10.0.0.5', '127.0.0.1', '::1']) | |
201 | ||
202 | >>> for host in hosts: | |
203 | ... if host.is_alive: | |
204 | ... print(f'{host.address} is alive!') | |
205 | ... | |
206 | ... else: | |
207 | ... print(f'{host.address} is dead!') | |
208 | ... | |
209 | 10.0.0.5 is dead! | |
210 | 127.0.0.1 is alive! | |
211 | ::1 is alive! | |
212 | ||
213 | See the `Host` class for details. | |
214 | ||
215 | ''' | |
216 | hosts = [] | |
217 | inactive_threads = [] | |
218 | active_threads = [] | |
219 | ||
220 | for i, address in enumerate(addresses): | |
221 | thread = PingThread( | |
222 | address=address, | |
223 | count=count, | |
224 | interval=interval, | |
225 | timeout=timeout, | |
226 | id=id + i, | |
227 | **kwargs) | |
228 | ||
229 | inactive_threads.append(thread) | |
230 | ||
231 | while inactive_threads: | |
232 | thread = inactive_threads.pop(0) | |
233 | thread.start() | |
234 | active_threads.append(thread) | |
235 | ||
236 | if (inactive_threads and | |
237 | len(active_threads) < max_threads): | |
238 | sleep(0.05) | |
239 | continue | |
240 | ||
241 | while active_threads: | |
242 | thread = active_threads.pop(0) | |
243 | thread.join() | |
244 | hosts.append(thread.host) | |
245 | ||
246 | return hosts | |
267 | _Socket = ICMPv4Socket | |
268 | ||
269 | id = id or unique_identifier() | |
270 | packets_sent = 0 | |
271 | rtts = [] | |
272 | ||
273 | with AsyncSocket(_Socket(source, privileged)) as sock: | |
274 | for sequence in range(count): | |
275 | if sequence > 0: | |
276 | await asyncio.sleep(interval) | |
277 | ||
278 | request = ICMPRequest( | |
279 | destination=address, | |
280 | id=id, | |
281 | sequence=sequence, | |
282 | **kwargs) | |
283 | ||
284 | try: | |
285 | sock.send(request) | |
286 | packets_sent += 1 | |
287 | ||
288 | reply = await sock.receive(request, timeout) | |
289 | reply.raise_for_status() | |
290 | ||
291 | rtt = (reply.time - request.time) * 1000 | |
292 | rtts.append(rtt) | |
293 | ||
294 | except ICMPLibError: | |
295 | pass | |
296 | ||
297 | return Host(address, packets_sent, rtts) |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
23 | 25 | <https://www.gnu.org/licenses/>. |
24 | 26 | ''' |
25 | 27 | |
26 | import socket | |
28 | import socket, asyncio | |
27 | 29 | from struct import pack, unpack |
28 | 30 | from time import time |
29 | 31 | |
30 | from .ip import IPv4Socket, IPv6Socket | |
31 | from .models import ICMPRequest, ICMPReply | |
32 | from .models import ICMPReply | |
32 | 33 | from .exceptions import * |
33 | from .utils import random_byte_message | |
34 | ||
35 | ||
36 | # Echo Request and Echo Reply messages -- RFC 792 / 4443 | |
37 | # | |
38 | # 0 1 2 3 | |
39 | # 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 | |
40 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
41 | # | Type | Code | Checksum | | |
42 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
43 | # | Identifier | Sequence Number | | |
44 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
45 | # | Data ... | |
46 | # +-+-+-+-+- | |
47 | # | |
48 | # ICMPv4 Error message -- RFC 792 | |
49 | # | |
50 | # 0 1 2 3 | |
51 | # 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 | |
52 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
53 | # | Type | Code | Checksum | | |
54 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
55 | # | Unused / Depends on the error | | |
56 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
57 | # | Internet Header + 64 bits of Original Data Datagram | | |
58 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
59 | # | |
60 | # ICMPv6 Error message -- RFC 4443 | |
61 | # | |
62 | # 0 1 2 3 | |
63 | # 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 | |
64 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
65 | # | Type | Code | Checksum | | |
66 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
67 | # | Unused / Depends on the error | | |
68 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
69 | # | Original packet without exceed the minimum IPv6 MTU | | |
70 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
71 | ||
72 | ||
73 | class ICMPConfig: | |
74 | IP_PROTOCOL = -1 | |
75 | IP_SOCKET = None | |
76 | ||
77 | ICMP_HEADER_OFFSET = -1 | |
78 | ICMP_CODE_OFFSET = ICMP_HEADER_OFFSET + 1 | |
79 | ICMP_CHECKSUM_OFFSET = ICMP_HEADER_OFFSET + 2 | |
80 | ICMP_ID_OFFSET = ICMP_HEADER_OFFSET + 4 | |
81 | ICMP_SEQUENCE_OFFSET = ICMP_HEADER_OFFSET + 6 | |
82 | ICMP_PAYLOAD_OFFSET = ICMP_HEADER_OFFSET + 8 | |
83 | ||
84 | ICMP_ECHO_REQUEST = -1 | |
85 | ICMP_ECHO_REPLY = -1 | |
86 | ||
87 | ||
88 | class ICMPv4Config(ICMPConfig): | |
89 | IP_PROTOCOL = 1 | |
90 | IP_SOCKET = IPv4Socket | |
91 | ||
92 | ICMP_HEADER_OFFSET = 20 | |
93 | ICMP_CODE_OFFSET = ICMP_HEADER_OFFSET + 1 | |
94 | ICMP_CHECKSUM_OFFSET = ICMP_HEADER_OFFSET + 2 | |
95 | ICMP_ID_OFFSET = ICMP_HEADER_OFFSET + 4 | |
96 | ICMP_SEQUENCE_OFFSET = ICMP_HEADER_OFFSET + 6 | |
97 | ICMP_PAYLOAD_OFFSET = ICMP_HEADER_OFFSET + 8 | |
98 | ||
99 | ICMP_ECHO_REQUEST = 8 | |
100 | ICMP_ECHO_REPLY = 0 | |
101 | ||
102 | ||
103 | class ICMPv6Config(ICMPConfig): | |
104 | IP_PROTOCOL = 58 | |
105 | IP_SOCKET = IPv6Socket | |
106 | ||
107 | ICMP_HEADER_OFFSET = 0 | |
108 | ICMP_CODE_OFFSET = ICMP_HEADER_OFFSET + 1 | |
109 | ICMP_CHECKSUM_OFFSET = ICMP_HEADER_OFFSET + 2 | |
110 | ICMP_ID_OFFSET = ICMP_HEADER_OFFSET + 4 | |
111 | ICMP_SEQUENCE_OFFSET = ICMP_HEADER_OFFSET + 6 | |
112 | ICMP_PAYLOAD_OFFSET = ICMP_HEADER_OFFSET + 8 | |
113 | ||
114 | ICMP_ECHO_REQUEST = 128 | |
115 | ICMP_ECHO_REPLY = 129 | |
34 | from .utils import * | |
116 | 35 | |
117 | 36 | |
118 | 37 | class ICMPSocket: |
119 | 38 | ''' |
120 | 39 | Base class for ICMP sockets. |
121 | 40 | |
122 | :type config: ICMPConfig | |
123 | :param config: The ICMP socket configuration used to create and | |
124 | read ICMP packets. | |
125 | ||
126 | :raises SocketPermissionError: If the permissions are insufficient | |
127 | to create the socket. | |
41 | :type address: str, optional | |
42 | :param address: The IP address from which you want to listen and | |
43 | send packets. By default, the socket listens on all interfaces. | |
44 | ||
45 | :type privileged: bool, optional | |
46 | :param privileged: When this option is enabled, the socket fully | |
47 | manages the exchanges and the structure of the ICMP packets. | |
48 | Disable this option if you want to instantiate and use the | |
49 | socket without root privileges and let the kernel handle ICMP | |
50 | headers. Default to True. | |
51 | Only available on Unix systems. Ignored on Windows. | |
52 | ||
53 | :raises SocketPermissionError: If the privileges are insufficient to | |
54 | create the socket. | |
55 | :raises SocketAddressError: If the requested address cannot be | |
56 | assigned to the socket. | |
57 | :raises ICMPSocketError: If another error occurs while creating the | |
58 | socket. | |
128 | 59 | |
129 | 60 | ''' |
130 | def __init__(self, config): | |
131 | self._socket = None | |
132 | self._config = config | |
133 | self._last_request = None | |
61 | __slots__ = '_sock', '_address', '_privileged' | |
62 | ||
63 | _IP_VERSION = -1 | |
64 | _ICMP_HEADER_OFFSET = -1 | |
65 | _ICMP_HEADER_REAL_OFFSET = -1 | |
66 | ||
67 | _ICMP_CODE_OFFSET = _ICMP_HEADER_OFFSET + 1 | |
68 | _ICMP_CHECKSUM_OFFSET = _ICMP_HEADER_OFFSET + 2 | |
69 | _ICMP_ID_OFFSET = _ICMP_HEADER_OFFSET + 4 | |
70 | _ICMP_SEQUENCE_OFFSET = _ICMP_HEADER_OFFSET + 6 | |
71 | _ICMP_PAYLOAD_OFFSET = _ICMP_HEADER_OFFSET + 8 | |
72 | ||
73 | _ICMP_ECHO_REQUEST = -1 | |
74 | _ICMP_ECHO_REPLY = -1 | |
75 | ||
76 | def __init__(self, address=None, privileged=True): | |
77 | self._sock = None | |
78 | self._address = address | |
79 | ||
80 | # The Linux kernel allows unprivileged users to use datagram | |
81 | # sockets (SOCK_DGRAM) to send ICMP requests. This feature is | |
82 | # now supported by the majority of Unix systems. | |
83 | # Windows is not compatible. | |
84 | self._privileged = privileged or PLATFORM_WINDOWS | |
134 | 85 | |
135 | 86 | try: |
136 | self._socket = config.IP_SOCKET( | |
137 | config.IP_PROTOCOL) | |
138 | ||
139 | except OSError: | |
140 | raise SocketPermissionError | |
87 | self._sock = self._create_socket( | |
88 | socket.SOCK_RAW if self._privileged else | |
89 | socket.SOCK_DGRAM) | |
90 | ||
91 | if address: | |
92 | self._sock.bind((address, 0)) | |
93 | ||
94 | except OSError as err: | |
95 | if err.errno in (1, 13, 10013): | |
96 | raise SocketPermissionError(privileged) | |
97 | ||
98 | if err.errno in (-9, 49, 99, 10049, 11001): | |
99 | raise SocketAddressError(address) | |
100 | ||
101 | raise ICMPSocketError(str(err)) | |
102 | ||
103 | def __enter__(self): | |
104 | ''' | |
105 | Return this object. | |
106 | ||
107 | ''' | |
108 | return self | |
109 | ||
110 | def __exit__(self, type, value, traceback): | |
111 | ''' | |
112 | Call the `close` method. | |
113 | ||
114 | ''' | |
115 | self.close() | |
141 | 116 | |
142 | 117 | def __del__(self): |
143 | 118 | ''' |
146 | 121 | ''' |
147 | 122 | self.close() |
148 | 123 | |
149 | def _create_header(self, type, code, checksum, id, sequence): | |
150 | ''' | |
151 | Create the ICMP header of a packet. | |
152 | ||
153 | ''' | |
154 | # B: 8 bits | |
155 | # H: 16 bits | |
156 | return pack('!2B3H', type, code, checksum, id, sequence) | |
124 | def _create_socket(self, type): | |
125 | ''' | |
126 | Create and return a new socket. Must be overridden. | |
127 | ||
128 | ''' | |
129 | raise NotImplementedError | |
130 | ||
131 | def _set_ttl(self, ttl): | |
132 | ''' | |
133 | Set the time to live of every IP packet originating from this | |
134 | socket. Must be overridden. | |
135 | ||
136 | ''' | |
137 | raise NotImplementedError | |
138 | ||
139 | def _set_traffic_class(self, traffic_class): | |
140 | ''' | |
141 | Set the DS Field (formerly TOS) or the Traffic Class field of | |
142 | every IP packet originating from this socket. Must be | |
143 | overridden. | |
144 | ||
145 | ''' | |
146 | raise NotImplementedError | |
157 | 147 | |
158 | 148 | def _checksum(self, data): |
159 | 149 | ''' |
160 | Calculate the checksum of a packet. | |
161 | ||
162 | ''' | |
150 | Compute the checksum of an ICMP packet. Checksums are used to | |
151 | verify the integrity of packets. | |
152 | ||
153 | ''' | |
154 | sum = 0 | |
163 | 155 | data += b'\x00' |
164 | end = len(data) - 1 | |
165 | sum = 0 | |
166 | ||
167 | for i in range(0, end, 2): | |
156 | ||
157 | for i in range(0, len(data) - 1, 2): | |
168 | 158 | sum += (data[i] << 8) + data[i + 1] |
169 | sum = (sum >> 16) + (sum & 0xffff) | |
159 | sum = (sum & 0xffff) + (sum >> 16) | |
170 | 160 | |
171 | 161 | sum = ~sum & 0xffff |
172 | 162 | |
174 | 164 | |
175 | 165 | def _create_packet(self, id, sequence, payload): |
176 | 166 | ''' |
177 | Create a packet. | |
178 | ||
179 | ''' | |
180 | type = self._config.ICMP_ECHO_REQUEST | |
181 | ||
182 | # Temporary header to calculate the checksum | |
183 | header = self._create_header(type, 0, 0, id, sequence) | |
167 | Build an ICMP packet from an identifier, a sequence number and | |
168 | a payload. | |
169 | ||
170 | This method returns the newly created ICMP header concatenated | |
171 | to the payload passed in parameters. | |
172 | ||
173 | ''' | |
174 | checksum = 0 | |
175 | ||
176 | # Temporary ICMP header to compute the checksum | |
177 | header = pack('!2B3H', self._ICMP_ECHO_REQUEST, 0, checksum, | |
178 | id, sequence) | |
179 | ||
184 | 180 | checksum = self._checksum(header + payload) |
185 | 181 | |
186 | # Definitive header | |
187 | header = self._create_header(type, 0, checksum, id, sequence) | |
182 | # Definitive ICMP header | |
183 | header = pack('!2B3H', self._ICMP_ECHO_REQUEST, 0, checksum, | |
184 | id, sequence) | |
188 | 185 | |
189 | 186 | return header + payload |
190 | 187 | |
191 | def _read_reply(self, packet, source, reply_time): | |
192 | ''' | |
193 | Read a reply from bytes. Return an `ICMPReply` object or `None` | |
194 | if the reply cannot be parsed. | |
195 | ||
196 | ''' | |
197 | bytes_received = ( | |
198 | len(packet) | |
199 | - self._config.ICMP_HEADER_OFFSET) | |
200 | ||
201 | if len(packet) < self._config.ICMP_CHECKSUM_OFFSET: | |
188 | def _parse_reply(self, packet, source, current_time): | |
189 | ''' | |
190 | Parse an ICMP reply from bytes. | |
191 | ||
192 | This method returns an `ICMPReply` object or `None` if the reply | |
193 | cannot be parsed. | |
194 | ||
195 | ''' | |
196 | # On Linux, the IP header is missing when a datagram socket is | |
197 | # used (SOCK_DGRAM). To keep the same behavior on all operating | |
198 | # systems including macOS which has this header, we add a | |
199 | # padding of the size of the missing IP header. | |
200 | if not self._privileged and PLATFORM_LINUX: | |
201 | packet = b'\x00' * self._ICMP_HEADER_OFFSET + packet | |
202 | ||
203 | bytes_received = len(packet) - self._ICMP_HEADER_OFFSET | |
204 | ||
205 | if len(packet) < self._ICMP_CHECKSUM_OFFSET: | |
202 | 206 | return None |
203 | 207 | |
204 | 208 | type, code = unpack('!2B', packet[ |
205 | self._config.ICMP_HEADER_OFFSET: | |
206 | self._config.ICMP_CHECKSUM_OFFSET]) | |
207 | ||
208 | if type != self._config.ICMP_ECHO_REPLY: | |
209 | packet = packet[self._config.ICMP_PAYLOAD_OFFSET:] | |
210 | ||
211 | if len(packet) < self._config.ICMP_PAYLOAD_OFFSET: | |
209 | self._ICMP_HEADER_OFFSET: | |
210 | self._ICMP_CHECKSUM_OFFSET]) | |
211 | ||
212 | if type != self._ICMP_ECHO_REPLY: | |
213 | packet = packet[ | |
214 | self._ICMP_PAYLOAD_OFFSET | |
215 | - self._ICMP_HEADER_OFFSET | |
216 | + self._ICMP_HEADER_REAL_OFFSET:] | |
217 | ||
218 | if len(packet) < self._ICMP_PAYLOAD_OFFSET: | |
212 | 219 | return None |
213 | 220 | |
214 | 221 | id, sequence = unpack('!2H', packet[ |
215 | self._config.ICMP_ID_OFFSET: | |
216 | self._config.ICMP_PAYLOAD_OFFSET | |
217 | ]) | |
218 | ||
219 | reply = ICMPReply( | |
222 | self._ICMP_ID_OFFSET: | |
223 | self._ICMP_PAYLOAD_OFFSET]) | |
224 | ||
225 | return ICMPReply( | |
220 | 226 | source=source, |
227 | family=self._IP_VERSION, | |
221 | 228 | id=id, |
222 | 229 | sequence=sequence, |
223 | 230 | type=type, |
224 | 231 | code=code, |
225 | 232 | bytes_received=bytes_received, |
226 | time=reply_time) | |
227 | ||
228 | return reply | |
233 | time=current_time) | |
229 | 234 | |
230 | 235 | def send(self, request): |
231 | 236 | ''' |
232 | Send a request to a host. | |
237 | Send an ICMP request message over the network to a remote host. | |
233 | 238 | |
234 | 239 | This operation is non-blocking. Use the `receive` method to get |
235 | 240 | the reply. |
236 | 241 | |
237 | 242 | :type request: ICMPRequest |
238 | :param request: The ICMP request you have created. | |
239 | ||
240 | :raises SocketBroadcastError: If a broadcast address is used | |
241 | and the corresponding option is not enabled on the socket | |
243 | :param request: The ICMP request you have created. If the socket | |
244 | is used in non-privileged mode on a Linux system, the | |
245 | identifier defined in the request will be replaced by the | |
246 | kernel. | |
247 | ||
248 | :raises SocketBroadcastError: If a broadcast address is used and | |
249 | the corresponding option is not enabled on the socket | |
242 | 250 | (ICMPv4 only). |
243 | 251 | :raises SocketUnavailableError: If the socket is closed. |
244 | 252 | :raises ICMPSocketError: If another error occurs while sending. |
245 | 253 | |
246 | 254 | ''' |
247 | if not self._socket: | |
255 | if not self._sock: | |
248 | 256 | raise SocketUnavailableError |
249 | 257 | |
250 | payload = request.payload | |
251 | ||
252 | if not payload: | |
253 | payload = random_byte_message( | |
254 | size=request.payload_size) | |
258 | payload = request.payload or \ | |
259 | random_byte_message(request.payload_size) | |
255 | 260 | |
256 | 261 | packet = self._create_packet( |
257 | 262 | id=request.id, |
258 | 263 | sequence=request.sequence, |
259 | 264 | payload=payload) |
260 | 265 | |
261 | self._socket.ttl = request.ttl | |
262 | self._socket.traffic_class = request.traffic_class | |
263 | ||
264 | request._time = time() | |
265 | ||
266 | 266 | try: |
267 | # The packet is actually the Ethernet payload (without the | |
268 | # IP header): the variable name will be changed in future | |
269 | # versions | |
270 | self._socket.send( | |
271 | payload=packet, | |
272 | address=request.destination, | |
273 | port=0) | |
274 | ||
275 | self._last_request = request | |
267 | self._set_ttl(request.ttl) | |
268 | self._set_traffic_class(request.traffic_class) | |
269 | ||
270 | request._time = time() | |
271 | self._sock.sendto(packet, (request.destination, 0)) | |
272 | ||
273 | # On Linux, the ICMP request identifier is replaced by the | |
274 | # kernel with a random port number when a datagram socket is | |
275 | # used (SOCK_DGRAM). So, we update the request created by | |
276 | # the user to take this new identifier into account. | |
277 | if not self._privileged and PLATFORM_LINUX: | |
278 | request._id = self._sock.getsockname()[1] | |
276 | 279 | |
277 | 280 | except PermissionError: |
278 | 281 | raise SocketBroadcastError |
280 | 283 | except OSError as err: |
281 | 284 | raise ICMPSocketError(str(err)) |
282 | 285 | |
283 | def receive(self): | |
284 | ''' | |
285 | Receive a reply from a host. | |
286 | def receive(self, request=None, timeout=2): | |
287 | ''' | |
288 | Receive an ICMP reply message from the socket. | |
286 | 289 | |
287 | 290 | This method can be called multiple times if you expect several |
288 | responses (as with a broadcast address). | |
291 | responses as with a broadcast address. | |
292 | ||
293 | :type request: ICMPRequest, optional | |
294 | :param request: The ICMP request to use to match the response. | |
295 | By default, all ICMP packets arriving on the socket are | |
296 | returned. | |
297 | ||
298 | :type timeout: int or float, optional | |
299 | :param timeout: The maximum waiting time for receiving the | |
300 | response in seconds. Default to 2. | |
301 | ||
302 | :rtype: ICMPReply | |
303 | :returns: An `ICMPReply` object representing the response of the | |
304 | desired destination or an upstream gateway. See the | |
305 | `ICMPReply` class for details. | |
289 | 306 | |
290 | 307 | :raises TimeoutExceeded: If no response is received before the |
291 | timeout defined in the request. | |
292 | This exception is also useful for stopping a possible loop | |
293 | in case of multiple responses. | |
308 | timeout specified in parameters. | |
294 | 309 | :raises SocketUnavailableError: If the socket is closed. |
295 | :raises ICMPSocketError: If another error occurs while | |
296 | receiving. | |
297 | ||
298 | :rtype: ICMPReply | |
299 | :returns: An `ICMPReply` object containing the reply of the | |
300 | desired destination. | |
301 | ||
302 | See the `ICMPReply` class for details. | |
303 | ||
304 | ''' | |
305 | if not self._socket: | |
310 | :raises ICMPSocketError: If another error occurs while receiving. | |
311 | ||
312 | ''' | |
313 | if not self._sock: | |
306 | 314 | raise SocketUnavailableError |
307 | 315 | |
308 | if not self._last_request: | |
309 | raise TimeoutExceeded(0) | |
310 | ||
311 | request = self._last_request | |
312 | ||
313 | current_time = time() | |
314 | self._socket.timeout = request.timeout | |
315 | timeout = current_time + request.timeout | |
316 | self._sock.settimeout(timeout) | |
317 | time_limit = time() + timeout | |
316 | 318 | |
317 | 319 | try: |
318 | 320 | while True: |
319 | packet, address, port = self._socket.receive() | |
320 | reply_time = time() | |
321 | ||
322 | if reply_time > timeout: | |
321 | response = self._sock.recvfrom(1024) | |
322 | current_time = time() | |
323 | ||
324 | packet = response[0] | |
325 | source = response[1][0] | |
326 | ||
327 | if current_time > time_limit: | |
323 | 328 | raise socket.timeout |
324 | 329 | |
325 | reply = self._read_reply( | |
330 | reply = self._parse_reply( | |
326 | 331 | packet=packet, |
327 | source=address, | |
328 | reply_time=reply_time) | |
329 | ||
330 | if (reply and request.id == reply.id and | |
332 | source=source, | |
333 | current_time=current_time) | |
334 | ||
335 | if (reply and not request or | |
336 | reply and request.id == reply.id and | |
331 | 337 | request.sequence == reply.sequence): |
332 | 338 | return reply |
333 | 339 | |
334 | 340 | except socket.timeout: |
335 | raise TimeoutExceeded(request.timeout) | |
341 | raise TimeoutExceeded(timeout) | |
336 | 342 | |
337 | 343 | except OSError as err: |
338 | 344 | raise ICMPSocketError(str(err)) |
342 | 348 | Close the socket. It cannot be used after this call. |
343 | 349 | |
344 | 350 | ''' |
345 | if self._socket: | |
346 | self._socket.close() | |
347 | self._socket = None | |
351 | if self._sock: | |
352 | self._sock.close() | |
353 | self._sock = None | |
354 | ||
355 | @property | |
356 | def blocking(self): | |
357 | ''' | |
358 | Indicate whether the socket is in blocking mode. | |
359 | Return a `boolean`. | |
360 | ||
361 | ''' | |
362 | return self._sock.getblocking() | |
363 | ||
364 | @blocking.setter | |
365 | def blocking(self, enable): | |
366 | return self._sock.setblocking(enable) | |
367 | ||
368 | @property | |
369 | def address(self): | |
370 | ''' | |
371 | The IP address from which the socket listens and sends packets. | |
372 | Return `None` if the socket listens on all interfaces. | |
373 | ||
374 | ''' | |
375 | return self._address | |
376 | ||
377 | @property | |
378 | def is_privileged(self): | |
379 | ''' | |
380 | Indicate whether the socket is running in privileged mode. | |
381 | Return a `boolean`. | |
382 | ||
383 | ''' | |
384 | return self._privileged | |
348 | 385 | |
349 | 386 | @property |
350 | 387 | def is_closed(self): |
351 | 388 | ''' |
352 | Indicate whether the socket is closed. Return a `boolean`. | |
353 | ||
354 | ''' | |
355 | return self._socket is None | |
389 | Indicate whether the socket is closed. | |
390 | Return a `boolean`. | |
391 | ||
392 | ''' | |
393 | return self._sock is None | |
394 | ||
395 | ||
396 | # Echo Request and Echo Reply messages RFC 792 | |
397 | # | |
398 | # 0 1 2 3 | |
399 | # 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 | |
400 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
401 | # | Type | Code | Checksum | | |
402 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
403 | # | Identifier | Sequence Number | | |
404 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
405 | # | Data ... | |
406 | # +-+-+-+-+- | |
407 | # | |
408 | # ICMPv4 Error message RFC 792 | |
409 | # | |
410 | # 0 1 2 3 | |
411 | # 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 | |
412 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
413 | # | Type | Code | Checksum | | |
414 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
415 | # | Unused / Depends on the error | | |
416 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
417 | # | Internet Header + 64 bits of Original Data Datagram | | |
418 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
356 | 419 | |
357 | 420 | |
358 | 421 | class ICMPv4Socket(ICMPSocket): |
359 | 422 | ''' |
360 | Socket for sending and receiving ICMPv4 packets. | |
361 | ||
362 | :raises SocketPermissionError: If the permissions are insufficient | |
363 | to create the socket. | |
423 | Class for sending and receiving ICMPv4 packets. | |
424 | ||
425 | :type address: str, optional | |
426 | :param address: The IP address from which you want to listen and | |
427 | send packets. By default, the socket listens on all interfaces. | |
428 | ||
429 | :type privileged: bool, optional | |
430 | :param privileged: When this option is enabled, the socket fully | |
431 | manages the exchanges and the structure of the ICMP packets. | |
432 | Disable this option if you want to instantiate and use the | |
433 | socket without root privileges and let the kernel handle ICMP | |
434 | headers. Default to True. | |
435 | Only available on Unix systems. Ignored on Windows. | |
436 | ||
437 | :raises SocketPermissionError: If the privileges are insufficient to | |
438 | create the socket. | |
439 | :raises SocketAddressError: If the requested address cannot be | |
440 | assigned to the socket. | |
441 | :raises ICMPSocketError: If another error occurs while creating the | |
442 | socket. | |
364 | 443 | |
365 | 444 | ''' |
366 | def __init__(self): | |
367 | super().__init__(ICMPv4Config) | |
445 | __slots__ = '_sock', '_address', '_privileged' | |
446 | ||
447 | _IP_VERSION = 4 | |
448 | _ICMP_HEADER_OFFSET = 20 | |
449 | _ICMP_HEADER_REAL_OFFSET = 20 | |
450 | ||
451 | _ICMP_CODE_OFFSET = _ICMP_HEADER_OFFSET + 1 | |
452 | _ICMP_CHECKSUM_OFFSET = _ICMP_HEADER_OFFSET + 2 | |
453 | _ICMP_ID_OFFSET = _ICMP_HEADER_OFFSET + 4 | |
454 | _ICMP_SEQUENCE_OFFSET = _ICMP_HEADER_OFFSET + 6 | |
455 | _ICMP_PAYLOAD_OFFSET = _ICMP_HEADER_OFFSET + 8 | |
456 | ||
457 | _ICMP_ECHO_REQUEST = 8 | |
458 | _ICMP_ECHO_REPLY = 0 | |
459 | ||
460 | def _create_socket(self, type): | |
461 | ''' | |
462 | Create and return a new socket. | |
463 | ||
464 | ''' | |
465 | return socket.socket( | |
466 | family=socket.AF_INET, | |
467 | type=type, | |
468 | proto=socket.IPPROTO_ICMP) | |
469 | ||
470 | def _set_ttl(self, ttl): | |
471 | ''' | |
472 | Set the time to live of every IP packet originating from this | |
473 | socket. | |
474 | ||
475 | ''' | |
476 | self._sock.setsockopt( | |
477 | socket.IPPROTO_IP, | |
478 | socket.IP_TTL, | |
479 | ttl) | |
480 | ||
481 | def _set_traffic_class(self, traffic_class): | |
482 | ''' | |
483 | Set the DS Field (formerly TOS) of every IP packet originating | |
484 | from this socket. | |
485 | ||
486 | Only available on Unix systems. Ignored on Windows. | |
487 | ||
488 | ''' | |
489 | # Not available on Windows | |
490 | if PLATFORM_WINDOWS: | |
491 | return | |
492 | ||
493 | self._sock.setsockopt( | |
494 | socket.IPPROTO_IP, | |
495 | socket.IP_TOS, | |
496 | traffic_class) | |
368 | 497 | |
369 | 498 | @property |
370 | 499 | def broadcast(self): |
377 | 506 | icmp_socket.broadcast = True |
378 | 507 | |
379 | 508 | ''' |
380 | return self._socket.broadcast | |
509 | return self._sock.getsockopt( | |
510 | socket.SOL_SOCKET, | |
511 | socket.SO_BROADCAST) > 0 | |
381 | 512 | |
382 | 513 | @broadcast.setter |
383 | def broadcast(self, allow): | |
384 | self._socket.broadcast = allow | |
514 | def broadcast(self, enable): | |
515 | self._sock.setsockopt( | |
516 | socket.SOL_SOCKET, | |
517 | socket.SO_BROADCAST, | |
518 | enable) | |
519 | ||
520 | ||
521 | # Echo Request and Echo Reply messages RFC 4443 | |
522 | # | |
523 | # 0 1 2 3 | |
524 | # 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 | |
525 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
526 | # | Type | Code | Checksum | | |
527 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
528 | # | Identifier | Sequence Number | | |
529 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
530 | # | Data ... | |
531 | # +-+-+-+-+- | |
532 | # | |
533 | # ICMPv6 Error message RFC 4443 | |
534 | # | |
535 | # 0 1 2 3 | |
536 | # 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 | |
537 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
538 | # | Type | Code | Checksum | | |
539 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
540 | # | Unused / Depends on the error | | |
541 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
542 | # | Original packet without exceed the minimum IPv6 MTU | | |
543 | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
544 | ||
545 | # Windows IPv6 compatibility | |
546 | if PLATFORM_WINDOWS: | |
547 | socket.IPPROTO_IPV6 = 41 | |
548 | socket.IPPROTO_ICMPV6 = 58 | |
385 | 549 | |
386 | 550 | |
387 | 551 | class ICMPv6Socket(ICMPSocket): |
388 | 552 | ''' |
389 | Socket for sending and receiving ICMPv6 packets. | |
390 | ||
391 | :raises SocketPermissionError: If the permissions are insufficient | |
392 | to create the socket. | |
553 | Class for sending and receiving ICMPv6 packets. | |
554 | ||
555 | :type address: str, optional | |
556 | :param address: The IP address from which you want to listen and | |
557 | send packets. By default, the socket listens on all interfaces. | |
558 | ||
559 | :type privileged: bool, optional | |
560 | :param privileged: When this option is enabled, the socket fully | |
561 | manages the exchanges and the structure of the ICMP packets. | |
562 | Disable this option if you want to instantiate and use the | |
563 | socket without root privileges and let the kernel handle ICMP | |
564 | headers. Default to True. | |
565 | Only available on Unix systems. Ignored on Windows. | |
566 | ||
567 | :raises SocketPermissionError: If the privileges are insufficient to | |
568 | create the socket. | |
569 | :raises SocketAddressError: If the requested address cannot be | |
570 | assigned to the socket. | |
571 | :raises ICMPSocketError: If another error occurs while creating the | |
572 | socket. | |
393 | 573 | |
394 | 574 | ''' |
395 | def __init__(self): | |
396 | super().__init__(ICMPv6Config) | |
575 | __slots__ = '_sock', '_address', '_privileged' | |
576 | ||
577 | _IP_VERSION = 6 | |
578 | _ICMP_HEADER_OFFSET = 0 | |
579 | _ICMP_HEADER_REAL_OFFSET = 40 | |
580 | ||
581 | _ICMP_CODE_OFFSET = _ICMP_HEADER_OFFSET + 1 | |
582 | _ICMP_CHECKSUM_OFFSET = _ICMP_HEADER_OFFSET + 2 | |
583 | _ICMP_ID_OFFSET = _ICMP_HEADER_OFFSET + 4 | |
584 | _ICMP_SEQUENCE_OFFSET = _ICMP_HEADER_OFFSET + 6 | |
585 | _ICMP_PAYLOAD_OFFSET = _ICMP_HEADER_OFFSET + 8 | |
586 | ||
587 | _ICMP_ECHO_REQUEST = 128 | |
588 | _ICMP_ECHO_REPLY = 129 | |
589 | ||
590 | def _create_socket(self, type): | |
591 | ''' | |
592 | Create and return a new socket. | |
593 | ||
594 | ''' | |
595 | return socket.socket( | |
596 | family=socket.AF_INET6, | |
597 | type=type, | |
598 | proto=socket.IPPROTO_ICMPV6) | |
599 | ||
600 | def _set_ttl(self, ttl): | |
601 | ''' | |
602 | Set the time to live of every IP packet originating from this | |
603 | socket. | |
604 | ||
605 | ''' | |
606 | # Not available on macOS when the privileged param. is disabled | |
607 | if PLATFORM_MACOS and not self._privileged: | |
608 | return | |
609 | ||
610 | self._sock.setsockopt( | |
611 | socket.IPPROTO_IPV6, | |
612 | socket.IPV6_UNICAST_HOPS, | |
613 | ttl) | |
614 | ||
615 | def _set_traffic_class(self, traffic_class): | |
616 | ''' | |
617 | Set the Traffic Class field of every IP packet originating from | |
618 | this socket. | |
619 | ||
620 | Only available on Unix systems. Ignored on Windows. | |
621 | ||
622 | ''' | |
623 | # Not available on Windows | |
624 | if PLATFORM_WINDOWS: | |
625 | return | |
626 | ||
627 | # Not available on macOS when the privileged param. is disabled | |
628 | if PLATFORM_MACOS and not self._privileged: | |
629 | return | |
630 | ||
631 | self._sock.setsockopt( | |
632 | socket.IPPROTO_IPV6, | |
633 | socket.IPV6_TCLASS, | |
634 | traffic_class) | |
635 | ||
636 | ||
637 | class AsyncSocket: | |
638 | ''' | |
639 | A wrapper for ICMP sockets which makes them asynchronous. | |
640 | ||
641 | :type icmp_sock: ICMPSocket | |
642 | :param icmp_sock: An ICMP socket. Once the wrapper is instantiated, | |
643 | this socket should no longer be used directly. | |
644 | ||
645 | ''' | |
646 | __slots__ = '_icmp_sock' | |
647 | ||
648 | def __init__(self, icmp_sock): | |
649 | self._icmp_sock = icmp_sock | |
650 | self._icmp_sock.blocking = False | |
651 | ||
652 | def __getattr__(self, name): | |
653 | ''' | |
654 | Return the specified attribute of the underlying ICMP socket. | |
655 | ||
656 | ''' | |
657 | if not self._icmp_sock: | |
658 | raise SocketUnavailableError | |
659 | ||
660 | return getattr(self._icmp_sock, name) | |
661 | ||
662 | def __enter__(self): | |
663 | ''' | |
664 | Return this object. | |
665 | ||
666 | ''' | |
667 | return self | |
668 | ||
669 | def __exit__(self, type, value, traceback): | |
670 | ''' | |
671 | Call the `close` method. | |
672 | ||
673 | ''' | |
674 | self.close() | |
675 | ||
676 | def __del__(self): | |
677 | ''' | |
678 | Call the `close` method. | |
679 | ||
680 | ''' | |
681 | self.close() | |
682 | ||
683 | async def receive(self, request=None, timeout=2): | |
684 | ''' | |
685 | Receive an ICMP reply message from the socket. | |
686 | ||
687 | This method can be called multiple times if you expect several | |
688 | responses as with a broadcast address. | |
689 | ||
690 | This method is non-blocking. | |
691 | ||
692 | :type request: ICMPRequest, optional | |
693 | :param request: The ICMP request to use to match the response. | |
694 | By default, all ICMP packets arriving on the socket are | |
695 | returned. | |
696 | ||
697 | :type timeout: int or float, optional | |
698 | :param timeout: The maximum waiting time for receiving the | |
699 | response in seconds. Default to 2. | |
700 | ||
701 | :rtype: ICMPReply | |
702 | :returns: An `ICMPReply` object representing the response of the | |
703 | desired destination or an upstream gateway. See the | |
704 | `ICMPReply` class for details. | |
705 | Unlike the `reveive` method of synchronous ICMP sockets, the | |
706 | returned `ICMPReply` object does not specify the IP address | |
707 | of the host that replied to the request message. | |
708 | ||
709 | :raises TimeoutExceeded: If no response is received before the | |
710 | timeout specified in parameters. | |
711 | :raises SocketUnavailableError: If the socket is closed. | |
712 | :raises ICMPSocketError: If another error occurs while receiving. | |
713 | ||
714 | ''' | |
715 | if not self._icmp_sock or not self._icmp_sock._sock: | |
716 | raise SocketUnavailableError | |
717 | ||
718 | loop = asyncio.get_running_loop() | |
719 | time_limit = time() + timeout | |
720 | remaining_time = timeout | |
721 | ||
722 | try: | |
723 | while True: | |
724 | packet = await asyncio.wait_for( | |
725 | loop.sock_recv(self._icmp_sock._sock, 1024), | |
726 | remaining_time) | |
727 | ||
728 | current_time = time() | |
729 | ||
730 | if current_time > time_limit: | |
731 | raise asyncio.TimeoutError | |
732 | ||
733 | reply = self._parse_reply( | |
734 | packet=packet, | |
735 | source=None, | |
736 | current_time=current_time) | |
737 | ||
738 | if (reply and not request or | |
739 | reply and request.id == reply.id and | |
740 | request.sequence == reply.sequence): | |
741 | return reply | |
742 | ||
743 | remaining_time = time_limit - current_time | |
744 | ||
745 | except asyncio.TimeoutError: | |
746 | raise TimeoutExceeded(timeout) | |
747 | ||
748 | except OSError as err: | |
749 | raise ICMPSocketError(str(err)) | |
750 | ||
751 | finally: | |
752 | if isinstance(loop, asyncio.SelectorEventLoop): | |
753 | loop.remove_reader(self._icmp_sock._sock) | |
754 | ||
755 | def detach(self): | |
756 | ''' | |
757 | Detach the socket from the wrapper and return it. The wrapper | |
758 | cannot be used after this call but the socket can be reused for | |
759 | other purposes. | |
760 | ||
761 | ''' | |
762 | icmp_sock = self._icmp_sock | |
763 | ||
764 | if self._icmp_sock: | |
765 | self._icmp_sock = None | |
766 | ||
767 | return icmp_sock | |
768 | ||
769 | def close(self): | |
770 | ''' | |
771 | Detach the underlying socket from the wrapper and close it. Both | |
772 | cannot be used after this call. | |
773 | ||
774 | ''' | |
775 | if self._icmp_sock: | |
776 | self.detach().close() | |
777 | ||
778 | @property | |
779 | def is_closed(self): | |
780 | ''' | |
781 | Indicate whether the underlying socket is closed or detached | |
782 | from this wrapper. Return a `boolean`. | |
783 | ||
784 | ''' | |
785 | return self._icmp_sock is None |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
28 | 30 | from .sockets import ICMPv4Socket, ICMPv6Socket |
29 | 31 | from .models import ICMPRequest, Hop |
30 | 32 | from .exceptions import * |
31 | from .utils import PID, resolve, is_ipv6_address | |
32 | ||
33 | ||
34 | def traceroute(address, count=3, interval=0.05, timeout=2, id=PID, | |
35 | traffic_class=0, max_hops=30, fast_mode=False, **kwargs): | |
33 | from .utils import * | |
34 | ||
35 | ||
36 | def traceroute(address, count=2, interval=0.05, timeout=2, first_hop=1, | |
37 | max_hops=30, fast=False, id=None, source=None, family=None, | |
38 | **kwargs): | |
36 | 39 | ''' |
37 | 40 | Determine the route to a destination host. |
38 | 41 | |
39 | 42 | The Internet is a large and complex aggregation of network hardware, |
40 | 43 | connected together by gateways. Tracking the route one's packets |
41 | follow can be difficult. This function utilizes the IP protocol | |
42 | time to live field and attempts to elicit an ICMP TIME_EXCEEDED | |
43 | response from each gateway along the path to some host. | |
44 | follow can be difficult. This function uses the IP protocol time to | |
45 | live field and attempts to elicit an ICMP Time Exceeded response | |
46 | from each gateway along the path to some host. | |
47 | ||
48 | This function requires root privileges to run. | |
44 | 49 | |
45 | 50 | :type address: str |
46 | :param address: The destination IP address. | |
47 | ||
48 | :type count: int | |
49 | :param count: (Optional) The number of ping to perform per hop. | |
50 | ||
51 | :type interval: int or float | |
52 | :param interval: (Optional) The interval in seconds between sending | |
53 | each packet. | |
54 | ||
55 | :type timeout: int or float | |
56 | :param timeout: (Optional) The maximum waiting time for receiving | |
57 | a reply in seconds. | |
58 | ||
59 | :type id: int | |
60 | :param id: (Optional) The identifier of the request. Used to match | |
61 | the reply with the request. In practice, a unique identifier is | |
62 | used for every ping process. | |
63 | ||
64 | :type traffic_class: int | |
65 | :param traffic_class: (Optional) The traffic class of packets. | |
66 | Provides a defined level of service to packets by setting the | |
67 | DS Field (formerly TOS) or the Traffic Class field of IP | |
68 | headers. Packets are delivered with the minimum priority by | |
69 | default (Best-effort delivery). | |
51 | :param address: The IP address, hostname or FQDN of the host to | |
52 | reach. For deterministic behavior, prefer to use an IP address. | |
53 | ||
54 | :type count: int, optional | |
55 | :param count: The number of ping to perform per hop. Default to 2. | |
56 | ||
57 | :type interval: int or float, optional | |
58 | :param interval: The interval in seconds between sending each packet. | |
59 | Default to 0.05. | |
60 | ||
61 | :type timeout: int or float, optional | |
62 | :param timeout: The maximum waiting time for receiving a reply in | |
63 | seconds. Default to 2. | |
64 | ||
65 | :type first_hop: int, optional | |
66 | :param first_hop: The initial time to live value used in outgoing | |
67 | probe packets. Default to 1. | |
68 | ||
69 | :type max_hops: int, optional | |
70 | :param max_hops: The maximum time to live (max number of hops) used | |
71 | in outgoing probe packets. Default to 30. | |
72 | ||
73 | :type fast: bool, optional | |
74 | :param fast: When this option is enabled and an intermediate router | |
75 | has been reached, skip to the next hop rather than perform | |
76 | additional requests. The `count` parameter then becomes the | |
77 | maximum number of requests in the event of no response. | |
78 | Default to False. | |
79 | ||
80 | :type id: int, optional | |
81 | :param id: The identifier of ICMP requests. Used to match the | |
82 | responses with requests. In practice, a unique identifier should | |
83 | be used for every traceroute process. The library handles this | |
84 | identifier itself by default. | |
85 | ||
86 | :type source: str, optional | |
87 | :param source: The IP address from which you want to send packets. | |
88 | By default, the interface is automatically chosen according to | |
89 | the specified destination. | |
90 | ||
91 | :type family: int, optional | |
92 | :param family: The address family if a hostname or FQDN is specified. | |
93 | Can be set to `4` for IPv4 or `6` for IPv6 addresses. By default, | |
94 | this function searches for IPv4 addresses first before searching | |
95 | for IPv6 addresses. | |
96 | ||
97 | Advanced (**kwags): | |
98 | ||
99 | :type payload: bytes, optional | |
100 | :param payload: The payload content in bytes. A random payload is | |
101 | used by default. | |
102 | ||
103 | :type payload_size: int, optional | |
104 | :param payload_size: The payload size. Ignored when the `payload` | |
105 | parameter is set. Default to 56. | |
106 | ||
107 | :type traffic_class: int, optional | |
108 | :param traffic_class: The traffic class of ICMP packets. | |
109 | Provides a defined level of service to packets by setting the DS | |
110 | Field (formerly TOS) or the Traffic Class field of IP headers. | |
111 | Packets are delivered with the minimum priority by default | |
112 | (Best-effort delivery). | |
70 | 113 | Intermediate routers must be able to support this feature. |
71 | 114 | Only available on Unix systems. Ignored on Windows. |
72 | 115 | |
73 | :type max_hops: int | |
74 | :param max_hops: (Optional) The maximum time to live (max number of | |
75 | hops) used in outgoing probe packets. | |
76 | ||
77 | :type fast_mode: bool | |
78 | :param fast_mode: (Optional) When this option is enabled and an | |
79 | intermediate router has been reached, skip to the next hop | |
80 | rather than perform additional requests. The `count` parameter | |
81 | then becomes the maximum number of requests in case of no | |
82 | responses. | |
83 | ||
84 | :param **kwargs: (Optional) Advanced use: arguments passed to | |
85 | `ICMPRequest` objects. | |
86 | ||
87 | :rtype: list of Hop | |
116 | :rtype: list[Hop] | |
88 | 117 | :returns: A list of `Hop` objects representing the route to the |
89 | desired host. The list is sorted in ascending order according | |
90 | to the distance (in terms of hops) that separates the remote | |
91 | host from the current machine. | |
92 | ||
93 | :raises SocketPermissionError: If the permissions are insufficient | |
94 | to create a socket. | |
118 | desired destination. The list is sorted in ascending order | |
119 | according to the distance, in terms of hops, that separates the | |
120 | remote host from the current machine. Gateways that do not | |
121 | respond to requests are not added to this list. | |
122 | ||
123 | :raises NameLookupError: If you pass a hostname or FQDN in | |
124 | parameters and it does not exist or cannot be resolved. | |
125 | :raises SocketPermissionError: If the privileges are insufficient to | |
126 | create the socket. | |
127 | :raises SocketAddressError: If the source address cannot be assigned | |
128 | to the socket. | |
129 | :raises ICMPSocketError: If another error occurs. See the | |
130 | `ICMPv4Socket` or `ICMPv6Socket` class for details. | |
95 | 131 | |
96 | 132 | Usage:: |
97 | 133 | |
101 | 137 | |
102 | 138 | >>> for hop in hops: |
103 | 139 | ... if last_distance + 1 != hop.distance: |
104 | ... print('Some routers are not responding') | |
140 | ... print('Some gateways are not responding') | |
105 | 141 | ... |
106 | 142 | ... print(f'{hop.distance} {hop.address} {hop.avg_rtt} ms') |
107 | 143 | ... |
108 | 144 | ... last_distance = hop.distance |
109 | ... | |
110 | 1 10.0.0.1 5.196 ms | |
111 | 2 194.149.169.49 7.552 ms | |
112 | 3 194.149.166.54 12.21 ms | |
113 | * Some routers are not responding | |
114 | 5 212.73.205.22 22.15 ms | |
115 | 6 1.1.1.1 13.59 ms | |
145 | ||
146 | 1 10.0.0.1 5.196 ms | |
147 | 2 194.149.169.49 7.552 ms | |
148 | 3 194.149.166.54 12.21 ms | |
149 | * Some gateways are not responding | |
150 | 5 212.73.205.22 22.15 ms | |
151 | 6 1.1.1.1 13.59 ms | |
116 | 152 | |
117 | 153 | See the `Hop` class for details. |
118 | 154 | |
119 | 155 | ''' |
120 | address = resolve(address) | |
156 | if is_hostname(address): | |
157 | address = resolve(address, family)[0] | |
121 | 158 | |
122 | 159 | if is_ipv6_address(address): |
123 | socket = ICMPv6Socket() | |
124 | ||
160 | _Socket = ICMPv6Socket | |
125 | 161 | else: |
126 | socket = ICMPv4Socket() | |
127 | ||
128 | ttl = 1 | |
162 | _Socket = ICMPv4Socket | |
163 | ||
164 | id = id or unique_identifier() | |
165 | ttl = first_hop | |
129 | 166 | host_reached = False |
130 | 167 | hops = [] |
131 | 168 | |
132 | while not host_reached and ttl <= max_hops: | |
133 | hop_address = None | |
134 | packets_sent = 0 | |
135 | packets_received = 0 | |
136 | ||
137 | min_rtt = float('inf') | |
138 | avg_rtt = 0.0 | |
139 | max_rtt = 0.0 | |
140 | ||
141 | for sequence in range(count): | |
142 | request = ICMPRequest( | |
143 | destination=address, | |
144 | id=id, | |
145 | sequence=sequence, | |
146 | timeout=timeout, | |
147 | ttl=ttl, | |
148 | traffic_class=traffic_class, | |
149 | **kwargs) | |
150 | ||
151 | try: | |
152 | socket.send(request) | |
153 | packets_sent += 1 | |
154 | ||
155 | reply = socket.receive() | |
156 | reply.raise_for_status() | |
157 | host_reached = True | |
158 | ||
159 | except TimeExceeded: | |
160 | sleep(interval) | |
161 | ||
162 | except ICMPLibError: | |
163 | continue | |
164 | ||
165 | hop_address = reply.source | |
166 | packets_received += 1 | |
167 | ||
168 | round_trip_time = (reply.time - request.time) * 1000 | |
169 | avg_rtt += round_trip_time | |
170 | min_rtt = min(round_trip_time, min_rtt) | |
171 | max_rtt = max(round_trip_time, max_rtt) | |
172 | ||
173 | if fast_mode: | |
174 | break | |
175 | ||
176 | if packets_received: | |
177 | avg_rtt /= packets_received | |
178 | ||
179 | hop = Hop( | |
180 | address=hop_address, | |
181 | min_rtt=min_rtt, | |
182 | avg_rtt=avg_rtt, | |
183 | max_rtt=max_rtt, | |
184 | packets_sent=packets_sent, | |
185 | packets_received=packets_received, | |
186 | distance=ttl) | |
187 | ||
188 | hops.append(hop) | |
189 | ||
190 | ttl += 1 | |
191 | ||
192 | socket.close() | |
169 | with _Socket(source) as sock: | |
170 | while not host_reached and ttl <= max_hops: | |
171 | reply = None | |
172 | packets_sent = 0 | |
173 | rtts = [] | |
174 | ||
175 | for sequence in range(count): | |
176 | request = ICMPRequest( | |
177 | destination=address, | |
178 | id=id, | |
179 | sequence=sequence, | |
180 | ttl=ttl, | |
181 | **kwargs) | |
182 | ||
183 | try: | |
184 | sock.send(request) | |
185 | packets_sent += 1 | |
186 | ||
187 | reply = sock.receive(request, timeout) | |
188 | rtt = (reply.time - request.time) * 1000 | |
189 | rtts.append(rtt) | |
190 | ||
191 | reply.raise_for_status() | |
192 | host_reached = True | |
193 | ||
194 | except TimeExceeded: | |
195 | sleep(interval) | |
196 | ||
197 | except ICMPLibError: | |
198 | break | |
199 | ||
200 | if reply: | |
201 | hop = Hop( | |
202 | address=reply.source, | |
203 | packets_sent=packets_sent, | |
204 | rtts=rtts, | |
205 | distance=ttl) | |
206 | ||
207 | hops.append(hop) | |
208 | ||
209 | ttl += 1 | |
193 | 210 | |
194 | 211 | return hops |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
23 | 25 | <https://www.gnu.org/licenses/>. |
24 | 26 | ''' |
25 | 27 | |
26 | import socket | |
28 | import socket, asyncio | |
29 | ||
30 | from threading import Lock | |
27 | 31 | from sys import platform |
28 | 32 | from os import getpid |
29 | 33 | from re import match |
30 | 34 | from random import choices |
31 | 35 | |
36 | from .exceptions import NameLookupError | |
37 | ||
32 | 38 | |
33 | 39 | PID = getpid() |
40 | PLATFORM_LINUX = platform == 'linux' | |
41 | PLATFORM_MACOS = platform == 'darwin' | |
34 | 42 | PLATFORM_WINDOWS = platform == 'win32' |
43 | ||
44 | _lock_id = Lock() | |
45 | _current_id = PID | |
35 | 46 | |
36 | 47 | |
37 | 48 | def random_byte_message(size): |
39 | 50 | Generate a random byte sequence of the specified size. |
40 | 51 | |
41 | 52 | ''' |
42 | bytes_available = ( | |
53 | sequence = choices( | |
43 | 54 | b'abcdefghijklmnopqrstuvwxyz' |
44 | 55 | b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' |
45 | b'1234567890' | |
46 | ) | |
56 | b'1234567890', k=size) | |
47 | 57 | |
48 | return bytes( | |
49 | choices(bytes_available, k=size) | |
50 | ) | |
58 | return bytes(sequence) | |
51 | 59 | |
52 | 60 | |
53 | def resolve(name): | |
61 | def unique_identifier(): | |
54 | 62 | ''' |
55 | Resolve a hostname or FQDN into an IP address. If several IP | |
56 | addresses are available, only the first one is returned. | |
57 | ||
58 | This function searches for IPv4 addresses first for compatibility | |
59 | reasons before searching for IPv6 addresses. | |
60 | ||
61 | If no address is found, the name specified in parameter is | |
62 | returned so as not to impact the operation of other functions. This | |
63 | behavior may change in future versions of icmplib. | |
63 | Generate a unique identifier between 0 and 65535. | |
64 | The first number generated will be equal to the PID + 1. | |
64 | 65 | |
65 | 66 | ''' |
66 | if is_ipv4_address(name) or is_ipv6_address(name): | |
67 | return name | |
67 | global _current_id | |
68 | 68 | |
69 | with _lock_id: | |
70 | _current_id += 1 | |
71 | _current_id &= 0xffff | |
72 | ||
73 | return _current_id | |
74 | ||
75 | ||
76 | def resolve(name, family=None): | |
77 | ''' | |
78 | Resolve a hostname or FQDN to an IP address. Depending on the name | |
79 | specified in parameters, several IP addresses may be returned. | |
80 | ||
81 | This function relies on the DNS name server configured on your | |
82 | operating system. | |
83 | ||
84 | :type name: str | |
85 | :param name: A hostname or a Fully Qualified Domain Name (FQDN). | |
86 | ||
87 | :type family: int, optional | |
88 | :param family: The address family. Can be set to `4` for IPv4 or `6` | |
89 | for IPv6 addresses. By default, this function searches for IPv4 | |
90 | addresses first for compatibility reasons (A DNS lookup) before | |
91 | searching for IPv6 addresses (AAAA DNS lookup). | |
92 | ||
93 | :rtype: list[str] | |
94 | :returns: A list of IP addresses corresponding to the name passed as | |
95 | a parameter. | |
96 | ||
97 | :raises NameLookupError: If the requested name does not exist or | |
98 | cannot be resolved. | |
99 | ||
100 | ''' | |
69 | 101 | try: |
70 | return socket.getaddrinfo( | |
102 | if family == 6: | |
103 | _family = socket.AF_INET6 | |
104 | else: | |
105 | _family = socket.AF_INET | |
106 | ||
107 | lookup = socket.getaddrinfo( | |
71 | 108 | host=name, |
72 | 109 | port=None, |
73 | family=socket.AF_INET, | |
74 | type=socket.SOCK_DGRAM | |
75 | )[0][4][0] | |
110 | family=_family, | |
111 | type=socket.SOCK_DGRAM) | |
112 | ||
113 | return [address[4][0] for address in lookup] | |
76 | 114 | |
77 | 115 | except OSError: |
78 | pass | |
116 | if not family: | |
117 | return resolve(name, 6) | |
79 | 118 | |
119 | raise NameLookupError(name) | |
120 | ||
121 | ||
122 | async def async_resolve(name, family=None): | |
123 | ''' | |
124 | Resolve a hostname or FQDN to an IP address. Depending on the name | |
125 | specified in parameters, several IP addresses may be returned. | |
126 | ||
127 | This function relies on the DNS name server configured on your | |
128 | operating system. | |
129 | ||
130 | This function is non-blocking. | |
131 | ||
132 | :type name: str | |
133 | :param name: A hostname or a Fully Qualified Domain Name (FQDN). | |
134 | ||
135 | :type family: int, optional | |
136 | :param family: The address family. Can be set to `4` for IPv4 or `6` | |
137 | for IPv6 addresses. By default, this function searches for IPv4 | |
138 | addresses first for compatibility reasons (A DNS lookup) before | |
139 | searching for IPv6 addresses (AAAA DNS lookup). | |
140 | ||
141 | :rtype: list[str] | |
142 | :returns: A list of IP addresses corresponding to the name passed as | |
143 | a parameter. | |
144 | ||
145 | :raises NameLookupError: If the requested name does not exist or | |
146 | cannot be resolved. | |
147 | ||
148 | ''' | |
80 | 149 | try: |
81 | return socket.getaddrinfo( | |
150 | if family == 6: | |
151 | _family = socket.AF_INET6 | |
152 | else: | |
153 | _family = socket.AF_INET | |
154 | ||
155 | loop = asyncio.get_running_loop() | |
156 | ||
157 | lookup = await loop.getaddrinfo( | |
82 | 158 | host=name, |
83 | 159 | port=None, |
84 | family=socket.AF_INET6, | |
85 | type=socket.SOCK_DGRAM | |
86 | )[0][4][0] | |
160 | family=_family, | |
161 | type=socket.SOCK_DGRAM) | |
162 | ||
163 | return [address[4][0] for address in lookup] | |
87 | 164 | |
88 | 165 | except OSError: |
89 | return name | |
166 | if not family: | |
167 | return await async_resolve(name, 6) | |
168 | ||
169 | raise NameLookupError(name) | |
170 | ||
171 | ||
172 | def is_hostname(name): | |
173 | ''' | |
174 | Indicate whether the specified name is a hostname or an FQDN. | |
175 | Return a `boolean`. | |
176 | ||
177 | ''' | |
178 | pattern = r'(?i)^([a-z0-9-]+|([a-z0-9_-]+[.])+[a-z]+)$' | |
179 | return match(pattern, name) is not None | |
90 | 180 | |
91 | 181 | |
92 | 182 | def is_ipv4_address(address): |
95 | 185 | Return a `boolean`. |
96 | 186 | |
97 | 187 | ''' |
98 | return match( | |
99 | r'^([0-9]{1,3}[.]){3}[0-9]{1,3}$', | |
100 | address | |
101 | ) is not None | |
188 | pattern = r'^([0-9]{1,3}[.]){3}[0-9]{1,3}$' | |
189 | return match(pattern, address) is not None | |
102 | 190 | |
103 | 191 | |
104 | 192 | def is_ipv6_address(address): |
0 | 0 | [metadata] |
1 | 1 | name = icmplib |
2 | version = 1.2.2 | |
3 | description = Easily forge ICMP packets and make your own ping and traceroute. | |
4 | keywords = pure, implementation, icmp, sockets, ping, multiping, traceroute, ipv4, ipv6, python3 | |
2 | version = 3.0.1 | |
3 | description = The power to forge ICMP packets and do ping and traceroute. | |
4 | keywords = icmp, sockets, ping, multiping, traceroute, async, asyncio, ipv4, ipv6, python, python3 | |
5 | 5 | |
6 | 6 | author = Valentin BELYN |
7 | 7 | author_email = [email protected] |
18 | 18 | Intended Audience :: Developers |
19 | 19 | License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) |
20 | 20 | Operating System :: OS Independent |
21 | Programming Language :: Python | |
21 | 22 | Programming Language :: Python :: 3 |
22 | 23 | Programming Language :: Python :: 3 :: Only |
23 | Programming Language :: Python :: 3.6 | |
24 | 24 | Programming Language :: Python :: 3.7 |
25 | 25 | Programming Language :: Python :: 3.8 |
26 | Programming Language :: Python :: 3.9 | |
27 | Programming Language :: Python :: 3.10 | |
26 | 28 | Topic :: Software Development :: Libraries |
27 | 29 | Topic :: Software Development :: Libraries :: Python Modules |
28 | 30 | |
29 | 31 | [options] |
30 | 32 | packages = icmplib |
31 | python_requires = >=3.6 | |
33 | python_requires = >=3.7 |
1 | 1 | icmplib |
2 | 2 | ~~~~~~~ |
3 | 3 | |
4 | The power to forge ICMP packets and do ping and traceroute. | |
5 | ||
4 | 6 | https://github.com/ValentinBELYN/icmplib |
5 | 7 | |
6 | :copyright: Copyright 2017-2020 Valentin BELYN. | |
8 | :copyright: Copyright 2017-2021 Valentin BELYN. | |
7 | 9 | :license: GNU LGPLv3, see the LICENSE for details. |
8 | 10 | |
9 | 11 | ~~~~~~~ |
25 | 27 | |
26 | 28 | from setuptools import setup |
27 | 29 | |
28 | setup() | |
30 | setup(name='icmplib') |