Initial release

This commit is contained in:
juyoung 2024-11-17 14:33:33 -08:00
commit 2678ab5396
9 changed files with 340 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.venv

113
README.md Normal file
View file

@ -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.
<p align="center"><img src="docs/assets/img/preview.png" width=80% alt=""></p>
## 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.<br><img src="./docs/assets/img/math.png" width=300px alt="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
}
```

3
conf.yml Normal file
View file

@ -0,0 +1,3 @@
ADMIN_USERNAME: admin
ADMIN_PASSWORD: Password
REFRESH_TIME: 10

0
device_data.log Normal file
View file

0
device_list.log Normal file
View file

BIN
docs/assets/img/math.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
docs/assets/img/preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

23
requirements.txt Normal file
View file

@ -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

200
wifeye.py Normal file
View file

@ -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()