1 | #!/usr/bin/python3 |
---|
2 | |
---|
3 | # Copyright 2020 Fiona Klute |
---|
4 | # |
---|
5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
---|
6 | # you may not use this file except in compliance with the License. |
---|
7 | # You may obtain a copy of the License at |
---|
8 | # |
---|
9 | # http://www.apache.org/licenses/LICENSE-2.0 |
---|
10 | # |
---|
11 | # Unless required by applicable law or agreed to in writing, software |
---|
12 | # distributed under the License is distributed on an "AS IS" BASIS, |
---|
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
---|
14 | # See the License for the specific language governing permissions and |
---|
15 | # limitations under the License. |
---|
16 | |
---|
17 | """Usage: python3 check_test_ips.py IP|HOSTNAME [...] |
---|
18 | |
---|
19 | Check if the given IPs, or IPs associated with given hostnames, are |
---|
20 | routable. Under the assumption that those IPs are local a positive |
---|
21 | result means they should be usable for tests. |
---|
22 | |
---|
23 | The script prints the first two (in order of arguments) usable |
---|
24 | addresses to sys.stdout. IPv6 addresses in the output are enclosed in |
---|
25 | square brackets. |
---|
26 | |
---|
27 | """ |
---|
28 | import socket |
---|
29 | import sys |
---|
30 | |
---|
31 | |
---|
32 | def try_connect(sockaddr): |
---|
33 | """Try to connect a UDP socket to the given address. "Connecting" a |
---|
34 | UDP socket means only setting the default destination for packets, |
---|
35 | so nothing is actually sent. Return True if the socket.connect() |
---|
36 | call was successful, False otherwise. |
---|
37 | |
---|
38 | For loopback this effectively tests if the address is really |
---|
39 | configured, to detect e.g. situations where a system may have "::1 |
---|
40 | localhost" in /etc/hosts, but actually has IPv6 disabled. |
---|
41 | |
---|
42 | """ |
---|
43 | af, socktype, proto, canonname, sa = sockaddr |
---|
44 | try: |
---|
45 | s = socket.socket(af, socktype, proto) |
---|
46 | s.connect(sa) |
---|
47 | except OSError: |
---|
48 | return False |
---|
49 | finally: |
---|
50 | s.close() |
---|
51 | return True |
---|
52 | |
---|
53 | |
---|
54 | if __name__ == "__main__": |
---|
55 | import argparse |
---|
56 | parser = argparse.ArgumentParser( |
---|
57 | description='Check if IPs/hostnames are routable') |
---|
58 | parser.add_argument('hosts', metavar='HOST', nargs='+', |
---|
59 | help='the hostnames/IPs to check') |
---|
60 | parser.add_argument('--quiet', '-q', action='store_true', |
---|
61 | help='disable debug output') |
---|
62 | parser.add_argument('--hostname', '-H', action='store_true', |
---|
63 | help='append socket.gethostname() to hosts') |
---|
64 | args = parser.parse_args() |
---|
65 | |
---|
66 | if args.hostname: |
---|
67 | args.hosts.append(socket.gethostname()) |
---|
68 | |
---|
69 | test_ips = [] |
---|
70 | for name in args.hosts: |
---|
71 | addrs = list(map(lambda t: t[-1][0], |
---|
72 | filter(try_connect, |
---|
73 | socket.getaddrinfo(name, 12345, |
---|
74 | proto=socket.IPPROTO_UDP)))) |
---|
75 | if not args.quiet: |
---|
76 | print(f'{name}: {addrs}', file=sys.stderr) |
---|
77 | test_ips += addrs |
---|
78 | |
---|
79 | print(' '.join(f'[{i}]' if ':' in i else i for i in test_ips[:2])) |
---|