This repository has no description
7.5 kB
290 lines
1import argparse
2import json
3import logging
4import os
5import re
6import signal
7import socket
8import struct
9import subprocess
10import time
11import urllib.request
12
13logger = logging.getLogger()
14got_signal = False
15
16
17def signal_handler(_, __):
18 global got_signal
19 got_signal = True
20
21
22def get_default_gateway() -> str:
23 """Read the default gateway directly from /proc."""
24 with open("/proc/net/route") as route_file:
25 for line in route_file:
26 fields = line.strip().split()
27 if fields[1] != '00000000' or not int(fields[3], 16) & 2:
28 continue
29
30 return socket.inet_ntoa(struct.pack("<L", int(fields[2], 16)))
31
32
33def connect(ssid, psk=None):
34 subprocess.run(
35 ["nmcli", "connection", "delete", "autowifi"],
36 stdout=subprocess.PIPE,
37 )
38 logging.info('connecting to %s', ssid)
39 if psk is None:
40 subprocess.run(
41 [
42 "nmcli",
43 "device",
44 "wifi",
45 "connect",
46 ssid,
47 "name",
48 "autowifi",
49 ],
50 stdout=subprocess.PIPE,
51 )
52 else:
53 subprocess.run(
54 [
55 "nmcli",
56 "device",
57 "wifi",
58 "connect",
59 ssid,
60 "name",
61 "autowifi",
62 "password",
63 psk,
64 ],
65 stdout=subprocess.PIPE,
66 )
67 time.sleep(5)
68
69
70def scan():
71 logging.debug('scanning wifis')
72 wifis_raw = subprocess.check_output([
73 "nmcli",
74 "-e", "no",
75 "-f", "ssid,signal,security,rsn-flags,bssid",
76 "-t",
77 "device", "wifi", "list",
78 "--rescan", "auto",
79 ])
80 wifis_list = wifis_raw.split(b'\n')
81 logging.debug('scanning wifis finished')
82 wifis = []
83 for line in wifis_list:
84 logging.debug(line)
85 ls = re.split(rb'(?<!\\):', line)
86 if len(ls) == 10:
87 wifis.append({
88 "ssid": ls[0],
89 "signal": int(ls[1]),
90 "crypto": ls[2],
91 "flags": ls[3],
92 "bssid": b':'.join(ls[4:]),
93 })
94 return wifis
95
96
97def get_known_wifis(wifi_dirs):
98 wifis_lines = []
99 for wdir in wifi_dirs:
100 for wfile in os.listdir(wdir):
101 with open(os.path.join(wdir, wfile)) as f:
102 wifis_lines += f.read().splitlines()
103 wifis = []
104 for line in wifis_lines:
105 ls = line.split('/')
106 wifis.append({"ssid": ls[0].encode(), "psk": ls[1].encode()})
107 return wifis
108
109
110def check_network():
111 logging.debug('checking network')
112
113 gateway = get_default_gateway()
114 if gateway:
115 response = subprocess.run(
116 [
117 'ping',
118 '-q',
119 '-c',
120 '1',
121 gateway,
122 ],
123 stdout=subprocess.PIPE,
124 )
125 if response.returncode == 0:
126 logging.debug('host %s is up', gateway)
127 return True
128 else:
129 logging.debug('host %s is down', gateway)
130 return False
131 else:
132 logging.debug('no gateway')
133 return False
134
135
136def check_internet():
137 logging.debug('checking internet')
138
139 try:
140 beacon = urllib.request.urlopen('http://krebsco.de/secret')
141 except Exception as e: # noqa
142 logging.debug(e)
143 logging.info('no internet exc')
144 return False
145 if beacon.read() == b'1337\n':
146 return True
147 logging.info('no internet oh')
148 return False
149
150
151def is_wifi_open(wifi):
152 if wifi['crypto'] == b'':
153 return True
154 else:
155 return False
156
157
158def is_wifi_seen(wifi, seen_wifis):
159 for seen_wifi in seen_wifis:
160 if seen_wifi["ssid"] == wifi["ssid"]:
161 return True
162 return False
163
164
165def run_hooks(dirs, wifi):
166 outputs = {}
167 for directory in dirs:
168 for hfile in os.listdir(directory):
169 try:
170 hook_output = subprocess.check_output([
171 str(os.path.join(directory, hfile)),
172 json.dumps(wifi)
173 ])
174 outputs[hfile] = hook_output
175 except OSError as e:
176 logging.debug(e)
177 logging.info('running plugin: {} failed'.format(hfile))
178 return outputs
179
180
181def stay_connected_loop(allowed_fails=5):
182 fails = 0
183 if check_network():
184 while True:
185 global got_signal
186 if got_signal:
187 logging.info('got disconnect signal')
188 got_signal = False
189 break
190 else:
191 if check_network():
192 time.sleep(10)
193 else:
194 fails += 1
195 if fails > allowed_fails:
196 logging.debug('network gone, giving up')
197 break
198 else:
199 logging.debug('hmm, missed ping, will try again shortly')
200 time.sleep(3)
201
202
203def main():
204 parser = argparse.ArgumentParser()
205
206 parser.add_argument(
207 '-x', '--scan-hooks',
208 dest='scan_hooks',
209 nargs='*',
210 help='directories with scan hooks (executed after scan)',
211 default=['/etc/autowifi/scan_hooks'],
212 )
213
214 parser.add_argument(
215 '-c', '--connection-hooks',
216 dest='connect_hooks',
217 nargs='*',
218 help='directories with connection hooks (executed after connection)',
219 default=['/etc/autowifi/connect_hooks'],
220 )
221
222 parser.add_argument(
223 '-w', '--wifi_dirs',
224 dest='wifi_dirs',
225 nargs='*',
226 help='directories with wifi configs',
227 default=['/etc/autowifi/wifis'],
228 )
229
230 parser.add_argument(
231 '-l', '--loglevel',
232 dest='loglevel',
233 help='loglevel to use',
234 default=logging.INFO,
235 )
236
237 parser.add_argument(
238 '-p', '--pidfile',
239 dest='pidfile',
240 help='file to write the pid to',
241 default=None,
242 )
243
244 parser.add_argument(
245 '--no-open',
246 dest='no_open_networks',
247 help='dont connect to open networks',
248 default=False,
249 action='store_true',
250 )
251
252 args = parser.parse_args()
253
254 wifi_dirs = args.wifi_dirs
255 logger.setLevel(args.loglevel)
256
257 signal.signal(signal.SIGUSR1, signal_handler)
258
259 if args.pidfile:
260 with open(args.pidfile, 'w+') as f:
261 f.write(str(os.getpid()))
262
263 while True:
264 time.sleep(1)
265 stay_connected_loop()
266 wifis = scan()
267 known_wifis = get_known_wifis(wifi_dirs)
268 known_seen_wifis = [
269 wifi for wifi in known_wifis if is_wifi_seen(wifi, wifis)
270 ]
271 for wifi in known_seen_wifis:
272 connect(wifi['ssid'], wifi['psk'])
273 if check_network():
274 logging.debug('connected to secure wifi, going into busy loop')
275 break
276 if check_network():
277 continue
278 logging.debug('no known secure wifi, connecting to open ones')
279 if not args.no_open_networks:
280 logging.debug('connecting to open wifis')
281 open_wifis = filter(is_wifi_open, wifis)
282 for wifi in open_wifis:
283 connect(wifi['ssid'])
284 if check_network():
285 run_hooks(args.connect_hooks, wifi)
286 break
287
288
289if __name__ == '__main__':
290 main()