From 2678ab539667b73939fd387b3a6ead63466a0443 Mon Sep 17 00:00:00 2001 From: juyoung Date: Sun, 17 Nov 2024 14:33:33 -0800 Subject: [PATCH] Initial release --- .gitignore | 1 + README.md | 113 ++++++++++++++++++++ conf.yml | 3 + device_data.log | 0 device_list.log | 0 docs/assets/img/math.png | Bin 0 -> 2022 bytes docs/assets/img/preview.png | Bin 0 -> 21605 bytes requirements.txt | 23 +++++ wifeye.py | 200 ++++++++++++++++++++++++++++++++++++ 9 files changed, 340 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 conf.yml create mode 100644 device_data.log create mode 100644 device_list.log create mode 100644 docs/assets/img/math.png create mode 100644 docs/assets/img/preview.png create mode 100644 requirements.txt create mode 100644 wifeye.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d17dae --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/README.md b/README.md new file mode 100644 index 0000000..b84b70d --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# Wifeye + +Track all Wi-Fi connected devices for real-time updates and better network security. This program works with routers that use 10.0.0.1 as the admin panel, like Xfinity routers. + +

+ +## Installation + +``` +virtualenv -p python .venv +source .venv/bin/activate +pip install -r requirements.txt +``` + +## Usage + +``` +python wifeye.py +``` + +### Configuration + +Edit `conf.yml`: + +```yaml +ADMIN_USERNAME: admin +ADMIN_PASSWORD: Password +REFRESH_TIME: 10 +``` + +- ADMIN_USERNAME: Username for the router's admin panel +- ADMIN_PASSWORD: Password for the router's admin panel +- REFRESH_TIME: How often the data refreshes (in seconds) + +### Log Files + +1. `device_data.log` saves the status of all devices every time it refreshes. Each record has the time and details about each device. + +**Example**: + +```json +{ + "timestamp": "2024-01-01 00:00:00", + "data": [ + { + "name": "phone", + "online": true, + "ip_type": "DHCP", + "rssi": -40, + "network": "Wi-Fi 5G", + "frequency": 5000, + "distance": 0.02, + "device_info": { + "ipv4_address": "10.0.0.2", + "ipv6_address": "1111:000:ffff:0000:0000:000:0000:1111", + "local_link_ipv6_address": "fe80::1111:0000:ffff:1111", + "mac_address": "01:00:01:00:00:01" + } + }, + { + "name": "laptop", + "online": false, + "ip_type": null, + "rssi": null, + "network": null, + "frequency": null, + "distance": null, + "device_info": { + ... + } + } + ] +} +{ + "timestamp": "2024-01-01 00:01:00", + "data": [ + { + ... +``` + +| Key | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| timestamp | Date and time of the log (YYYY-MM-DD HH:mm:ss) | +| data | Array of device objects | +| name | Device name (e.g., "Phone") | +| online | Connection status (true for online, false for offline) | +| ip_type | Type of IP allocation (e.g., "DHCP" or "Reserved") | +| rssi | Signal strength (dBm) | +| network | Wi-Fi network name (e.g., "Wi-Fi 5G" or "Wi-Fi 2.4G") | +| frequency | Wi-Fi frequency in MHz | +| distance | Distance to the router in meters.
The distance is equal to ten raised to the power of the fraction where the numerator is 30 minus the received signal strength indicator (RSSI) minus twenty times the base ten logarithm of the frequency minus 32.44, and the denominator is 20. | +| device_info | Detailed device information, including: | +| ipv4_address | Assigned IPv4 address | +| ipv6_address | Assigned IPv6 address | +| local_link_ipv6_address | Local link IPv6 address | +| mac_address | Device MAC address | + +2. `device_list.log` keeps a list of all unique devices and tracks when they go online or offline, including the time of each change. + +**Example**: + +```json +{ + "timestamp": "2024-01-01 00:00:00", + "name": "phone", + "online": true +} +{ + "timestamp": "2024-01-01 00:01:00", + "name": "phone", + "online": false +} +``` diff --git a/conf.yml b/conf.yml new file mode 100644 index 0000000..c743309 --- /dev/null +++ b/conf.yml @@ -0,0 +1,3 @@ +ADMIN_USERNAME: admin +ADMIN_PASSWORD: Password +REFRESH_TIME: 10 diff --git a/device_data.log b/device_data.log new file mode 100644 index 0000000..e69de29 diff --git a/device_list.log b/device_list.log new file mode 100644 index 0000000..e69de29 diff --git a/docs/assets/img/math.png b/docs/assets/img/math.png new file mode 100644 index 0000000000000000000000000000000000000000..0f83cc1d04b2ee19f6b0c9c7d4c7033203bd1193 GIT binary patch literal 2022 zcmV!}@Nt@+PIqJ?@l}}UR1qY0=fFzYMnM@{=$z<~BvHf$$kD&Fm*VaxwK!rj#((B`uz>xu<1mg(FMkyBo zKsjf{Do{4T5fao-{l=*KrbZC3l*g7j9ckN z-vMX?K$jAUv?>lO&UtO^)IEiO(2ex^Xr#xHNqK|d0uu+1ad730z(k=qy3rj2bHY&& zNXSy9EO%7Hm*nPC&y zRlnt-*-48c_Cc`k$=++DQ}=N1U+6}9W$3yX22+$}P;rbq1|+p^1vh-Z93%rE*fzBf zf)okto;_jD(A}Ctk!D-aN ztKz8WoY&S)by&7ax{+R*DgaUxKw9sZ;1C{#RRNf81Z`mb<`I!=`GfhcsWB02rR(-E zc}hv>pf-Lm#|i){jFxWmiDw{%P+m(H{hCn-o|lV5k(Ubjtk>2~b!rF(9C;(XH0;W$ z76Oo+*%gPZ>we0Tif#q2so)4|X_-AcW#5!|Yes;&YQN0w57D{?qa%ri6_4hytK`Cf zc*+*k)oW7qQh1aO{bE_xTe@QQrAn=*y|#9$m|K zCqslznlbRYT!Vmm5KMP`BI`^rgWywn+!IW9eDF*_AppEge|#nq<2??w`FiFPAub+P z2;@Sh(;=S?Nt*M(jDqQc$>c{UlYh(%g2{ZsolGW^$z(El2D?lr^Tol(vCniee+~9= zJZ3tX-;+L*eWsJ?laFJc>16uku_fRF4(k{Dh#S@* z;BefYhc(LjROyZS9RzA!6vrVm2wsu28BgZ1KlJb{xO?;^fLKQmq(!S&V7Q`Dh^09H zRS?Xv_%%FueM>NAcyNmdr@J6fcijg8JY=n_WKDeP>0k_g%nxwCM{C>1bcu`(NokQr z5D37vGQhha*v{i^h$n)eE+>ka?&0?cA8!c~01JkKjnM~u1c3n_<63$Mf{H<{xIrP#>=AldyLi$7o}^PLW~TAXrCU8QyMUhgjtPk9I*Ik(P?* zH_YFo)PATC1t=7nV422aA|w=KKn(`Sn%xBfjpRL46yR0-mR{incg(ymzYMu06KU!M zz&&SxO;fT$xKNrJ1MF4RSepxbYt31`wQ4|iO~n`hw5Be8OYwTC!>JpoumeoHu78JVHZxh$oSp=UVXo&`jMhnY=Oo0q{;#*EEjqcK`qY07*qoM6N<$ Ef{G5V#Q*>R literal 0 HcmV?d00001 diff --git a/docs/assets/img/preview.png b/docs/assets/img/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..1b3232d2d0acaa0019faa82029e08963d1595058 GIT binary patch literal 21605 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

jCF(}nq;ggE|SUTLoX*+|?#8x+xhDVUg%oA=ng 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 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e9a8368 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +idna==3.10 +importlib_metadata==8.5.0 +itsdangerous==2.2.0 +Jinja2==3.1.4 +lxml==5.3.0 +markdown-it-py==3.0.0 +MarkupSafe==3.0.2 +mdurl==0.1.2 +Pygments==2.18.0 +PyYAML==6.0.2 +requests==2.32.3 +rich==13.9.2 +soupsieve==2.6 +typing_extensions==4.12.2 +urllib3==2.2.3 +wcwidth==0.2.13 +Werkzeug==3.0.4 +zipp==3.20.2 + diff --git a/wifeye.py b/wifeye.py new file mode 100644 index 0000000..ec42af5 --- /dev/null +++ b/wifeye.py @@ -0,0 +1,200 @@ +import re +import requests +import math +import json +import time +import os +import yaml +from lxml import html +from datetime import datetime +from rich.console import Console +from rich.table import Table + +with open('conf.yml', 'r') as file: + config = yaml.safe_load(file) + +ADMIN_USERNAME = config['ADMIN_USERNAME'] +ADMIN_PASSWORD = config['ADMIN_PASSWORD'] +REFRESH_TIME = config['REFRESH_TIME'] + +session = requests.Session() +console = Console() + +base_url = 'http://10.0.0.1' +login_page_scope = '/check.php' +devices_page_scope = '/connected_devices_computers.php' + +payload = { + 'username': ADMIN_USERNAME, + 'password': ADMIN_PASSWORD +} + +# log in +def login(): + response = session.post(base_url + login_page_scope, data=payload) + + if response.ok: + print("Login successful") + else: + print("Login failed") + return False + return True + +# fetch devices information +def fetch_devices_data(): + devices_url = base_url + devices_page_scope + devices_response = session.get(devices_url) + tree = html.fromstring(devices_response.content) + + def device_info(device_info): + info_dict = {} + for info in device_info: + k = info.xpath('./b//text()')[0].strip() if info.xpath('./b//text()') else None + v = info.xpath('.//text()')[1].strip() if len(info.xpath('.//text()')) > 1 else None + if k and v: + info_dict[k.lower().replace(" ", "_")] = v.strip() + return info_dict + + devices_data = { + "online_devices": [], + "offline_devices": [] + } + + # process online devices + online_devices = tree.xpath('//div[@id="online-private"]/table//tr')[1:-1] + for row in online_devices: + host_name = row.xpath('.//td[@headers="host-name"]/a//text()')[0].strip() + dhcp_or_reserved = row.xpath('.//td[@headers="dhcp-or-reserved"]//text()')[0].strip() + rssi_text = row.xpath('.//td[@headers="rssi-level"]//text()')[0].strip() + + try: + rssi = int(float(rssi_text.replace(" dBm", ""))) + except ValueError: + print(f"Invalid RSSI value for {host_name}: {rssi_text}") + rssi = None + + connection_type = row.xpath('.//td[@headers="connection-type"]//text()')[0].strip() + frequency = int(float(re.findall(r'\d+\.?\d*', connection_type)[0]) * 1000) if connection_type else None + distance = round(10 ** ((30 - rssi - (20 * math.log10(frequency)) - 32.44) / 20), 2) if rssi is not None and frequency is not None else None + + device_entry = { + "name": host_name, + "online": True, + "ip_type": dhcp_or_reserved, + "rssi": rssi, + "network": connection_type, + "frequency": frequency, + "distance": distance, + "device_info": device_info(row.xpath('.//td[@headers="host-name"]//div[@class="device-info"]/dl/dd')) + } + + devices_data["online_devices"].append(device_entry) + + # process offline devices + offline_devices = tree.xpath('//div[@id="offline-private"]/table//tr')[1:-1] + for row in offline_devices: + host_name = row.xpath('.//td[@headers="offline-device-host-name"]/a//text()')[0].strip() + + device_entry = { + "name": host_name, + "online": False, + "ip_type": None, + "rssi": None, + "network": None, + "frequency": None, + "distance": None, + "device_info": device_info(row.xpath('.//td[@headers="offline-device-host-name"]//div[@class="device-info"]/dl/dd')) + } + + devices_data["offline_devices"].append(device_entry) + + # sort online devices by distance + known_distances = [device for device in devices_data["online_devices"] if device["distance"] is not None] + unknown_distances = [device for device in devices_data["online_devices"] if device["distance"] is None] + + sorted_known_distances = sorted(known_distances, key=lambda x: x["distance"]) + devices_data["online_devices"] = sorted_known_distances + unknown_distances + + return devices_data + +# format keys +def format_keys(data): + return {k.lower().replace(" ", "_"): v for k, v in data.items()} + +# log data +def log_devices_data(devices_data): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + combined_devices = devices_data["online_devices"] + devices_data["offline_devices"] + + # format keys for each device entry + formatted_devices = [format_keys(device) for device in combined_devices] + log_entry = {"timestamp": timestamp, "data": formatted_devices} + + with open("device_data.log", "a") as log_file: + log_file.write(json.dumps(log_entry) + "\n") + +# log status changes +def log_device_status_change(device_name, online): + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + log_entry = {"timestamp": timestamp, "name": device_name, "online": online} + + with open("device_list.log", "a") as log_file: + log_file.write(json.dumps(format_keys(log_entry)) + "\n") + +# display devices +def display_devices(devices_data, current_device_status): + os.system('cls' if os.name == 'nt' else 'clear') + + header = f"{'Name':<20}{'Online':<10}{'IP Type':<15}{'RSSI':<10}{'Network':<20}{'Frequency (MHz)':<20}{'Distance (m)':<15}{'Last Activity':<20}" + print(header) + + def format_device_row(device, last_activity): + return f"{device['name']:<20}{str(device['online']):<10}{(device['ip_type'] if device['ip_type'] is not None else 'null'):<15}{(str(device['rssi']) if device['rssi'] is not None else 'null'):<10}{(device['network'] if device['network'] is not None else 'null'):<20}{(str(device['frequency']) if device['frequency'] is not None else 'null'):<20}{(str(device['distance']) if device['distance'] is not None else 'null'):<15}{(last_activity if last_activity is not None else 'null'):<20}" + + # add online devices + for device in devices_data["online_devices"]: + last_activity = current_device_status.get(device["name"], {}).get("last_activity", None) + print(format_device_row(device, last_activity)) + + # add offline devices + for device in devices_data["offline_devices"]: + last_activity = current_device_status.get(device["name"], {}).get("last_activity", None) + print(format_device_row(device, last_activity)) + +# main +if login(): + current_device_status = {} + + try: + while True: + devices_data = fetch_devices_data() + if devices_data: + log_devices_data(devices_data) + + new_device_status = {} + for d in devices_data["online_devices"] + devices_data["offline_devices"]: + device_name = d["name"] + online = d["online"] + if device_name in current_device_status: + new_device_status[device_name] = current_device_status[device_name] + if current_device_status[device_name]["online"] != online: + new_device_status[device_name]["last_activity"] = datetime.now().strftime("%y-%m-%d %H:%M:%S") + # log only on status change + if online != current_device_status[device_name]["online"]: + log_device_status_change(device_name, online) + new_device_status[device_name]["online"] = online + else: + new_device_status[device_name] = { + "online": online, + "last_activity": datetime.now().strftime("%y-%m-%d %H:%M:%S") + } + log_device_status_change(device_name, online) + + current_device_status = new_device_status + display_devices(devices_data, current_device_status) + # refesh + time.sleep(REFRESH_TIME) + except KeyboardInterrupt: + print("Stopped by user.") + finally: + session.close()