Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable backend/netcontrol communication #30

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 84 additions & 45 deletions backend/langate/modules/netcontrol.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,91 @@
import socket, struct
import pickle
import threading
from ..settings import NETCONTROL_SOCKET_FILE
import requests
import subprocess
import logging

lock = threading.Lock()
GET_REQUESTS = ["get_mac", "get_ip", '']
POST_REQUESTS = ["connect_user"]
DELETE_REQUESTS = ["disconnect_user"]
PUT_REQUESTS = ["set_mark"]

class NetworkDaemonError(RuntimeError):
""" every error originating from netcontrol raise this exception """
class Netcontrol:
"""
SkytAsul marked this conversation as resolved.
Show resolved Hide resolved
Class which interacts with the netcontrol API.
"""
def request(self, endpoint='', args={}):
"""
Make a given request to the netcontrol API.
"""
response = None

def _send(sock, data):
pack = struct.pack('>I', len(data)) + data
sock.sendall(pack)
# Make the request
try:
# Check the type of request
if endpoint in GET_REQUESTS:
response = requests.get(self.REQUEST_URL + endpoint, params=args)
elif endpoint in POST_REQUESTS:
response = requests.post(self.REQUEST_URL + endpoint, params=args)
elif endpoint in DELETE_REQUESTS:
response = requests.delete(self.REQUEST_URL + endpoint, params=args)
elif endpoint in PUT_REQUESTS:
response = requests.put(self.REQUEST_URL + endpoint, params=args)

response.raise_for_status()
return response.json()

except requests.exceptions.ConnectionError:
raise requests.HTTPError("Could not connect to the netcontrol API.")
except requests.exceptions.Timeout:
raise requests.HTTPError("The request to the netcontrol API timed out.")

def _recv_bytes(sock, size):
data = b''
while len(data) < size:
r = sock.recv(size - len(data))
if not r:
return None
data += r
return data
def check_api(self):
"""
Check if the netcontrol API is running.
"""
self.logger.info("Checking connection with the netcontrol API...")
return self.request()

def _recv(sock):
data_length_r = _recv_bytes(sock, 4)
def get_mac(self, ip: str):
"""
Get the MAC address of the device with the given IP address.
"""
self.logger.info(f"Getting MAC address of {ip}...")
return self.request("get_mac", {"ip": ip})["mac"]

def get_ip(self, mac: str):
"""
Get the IP address of the device with the given MAC address.
"""
self.logger.info(f"Getting IP address of {mac}...")
return self.request("get_ip", {"mac": mac})["ip"]

def connect_user(self, mac: str, mark: int, name: str):
"""
Connect the user with the given MAC address.
"""
self.logger.info(f"Connecting user with MAC address {mac} ({name})...")
return self.request("connect_user", {"mac": mac, "mark": mark, "name": name})

def disconnect_user(self, mac: str):
"""
Disconnect the user with the given MAC address.
"""
self.logger.info(f"Disconnecting user with MAC address {mac}...")
return self.request("disconnect_user", {"mac": mac})

def set_mark(self, mac: str, mark: int):
"""
Set the mark of the user with the given MAC address.
"""
self.logger.info(f"Setting mark of user with MAC address {mac} to {mark}...")
return self.request("set_mark", {"mac": mac, "mark": mark})

def __init__(self):
"""
Initialize HOST_IP to the docker's default route, set up REQUEST_URL and check the connection with the netcontrol API.
"""
self.HOST_IP = subprocess.run(["/sbin/ip", "route"], capture_output=True).stdout.decode("utf-8").split()[2]
self.REQUEST_URL = f"http://{self.HOST_IP}:6784/"

if not data_length_r:
return None
self.logger = logging.getLogger(__name__)

data_length = struct.unpack('>I', data_length_r)[0]
return _recv_bytes(sock, data_length)


def communicate(payload):
with lock:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(NETCONTROL_SOCKET_FILE)
r = pickle.dumps(payload)
_send(sock, r)

response_r = _recv(sock)
response = pickle.loads(response_r)

if response["success"]:
return response

else:
raise NetworkDaemonError(response["message"])

def query(q, opts = {}):
b = { "query": q }
return communicate({ **b, **opts })
#self.check_api()
38 changes: 21 additions & 17 deletions backend/langate/network/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
Network module. This module is responsible for the device and user connexion management.
"""

import requests
import sys, logging
import os

from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _

from ..modules import netcontrol
from langate.settings import netcontrol
from langate.settings import SETTINGS

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -49,16 +50,18 @@ def ready(self):

for dev in Device.objects.all():
userdevice = UserDevice.objects.filter(mac=dev.mac).first()
if userdevice is not None:
connect_res = netcontrol.query("connect_user", {"mac": userdevice.mac, "name": userdevice.user.username})
else:
connect_res = netcontrol.query("connect_user", {"mac": dev.mac, "name": dev.name})
if not connect_res["success"]:
logger.info("[PortalConfig] Could not connect device %s", dev.mac)
try:
if userdevice is not None:
connect_res = netcontrol.connect_user(userdevice.mac, userdevice.mark, userdevice.user.username)
else:
connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name)
except requests.HTTPError as e:
logger.info("[PortalConfig] {e}")

mark_res = netcontrol.query("set_mark", {"mac": dev.mac, "mark": dev.mark})
if not mark_res["success"]:
logger.info("[PortalConfig] Could not set mark for device %s", dev.name)
try:
mark_res = netcontrol.set_mark(dev.mac, dev.mark)
except requests.HTTPError as e:
logger.info("[PortalConfig] {e}")

logger.info(_("[PortalConfig] Add default whitelist devices to the ipset"))
if os.path.exists("assets/misc/whitelist.txt"):
Expand All @@ -75,13 +78,14 @@ def ready(self):
else:
dev.whitelisted = True
dev.save()
try:
connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name)
except requests.HTTPError as e:
logger.info("[PortalConfig] {e}")

connect_res = netcontrol.query("connect_user", {"mac": dev.mac, "name": dev.name})
if not connect_res["success"]:
logger.info("[PortalConfig] Could not connect device %s", dev.mac)

mark_res = netcontrol.query("set_mark", {"mac": dev.mac, "mark": mark})
if not mark_res["success"]:
logger.info("[PortalConfig] Could not set mark for device %s", dev.name)
try:
mark_res = netcontrol.set_mark(dev.mac, mark)
except requests.HTTPError as e:
logger.info("[PortalConfig] {e}")
else:
logger.error("[PortalConfig] Invalid line in whitelist.txt: %s", line)
90 changes: 63 additions & 27 deletions backend/langate/network/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import requests
import random, logging
import re

Expand All @@ -8,7 +9,7 @@
from django.core.exceptions import ValidationError

from langate.user.models import User
from langate.modules import netcontrol
from langate.settings import netcontrol
from langate.settings import SETTINGS

from .utils import generate_dev_name, get_mark
Expand Down Expand Up @@ -64,16 +65,25 @@ def create_device(mac, name, whitelisted=False, mark=None):
# Validate the MAC address
validate_mac(mac)

netcontrol.query("connect", { "mac": mac, "name": name })
netcontrol.query("set_mark", { "mac": mac, "mark": mark })
logger.info("Connected device %s (the mac %s has been connected)", name, mac)
try:
netcontrol.connect_user(mac, mark, name)
logger.info("Connected device %s (the mac %s has been connected)", name, mac)
except requests.HTTPError as e:
raise ValidationError(
_("Could not connect user.")
) from e

try:
device = Device.objects.create(mac=mac, name=name, whitelisted=whitelisted, mark=mark)
device.save()
return device
except Exception as e:
netcontrol.query("disconnect_user", { "mac": mac })
try:
netcontrol.disconnect_user(mac)
except requests.HTTPError as e:
raise ValidationError(
_("Could not disconnect user.")
) from e
raise ValidationError(
_("There was an error creating the device. Please try again.")
) from e
Expand All @@ -83,8 +93,13 @@ def delete_device(mac):
"""
Delete a device with the given mac address
"""
netcontrol.query("disconnect_user", { "mac": mac })
logger.info("Disconnected device %s from the internet.", mac)
try:
netcontrol.disconnect_user(mac)
logger.info("Disconnected device %s from the internet.", mac)
except requests.HTTPError as e:
raise ValidationError(
_("Could not disconnect user.")
) from e

device = Device.objects.get(mac=mac)
device.delete()
Expand All @@ -98,30 +113,42 @@ def create_user_device(user: User, ip, name=None):
if not name:
name = generate_dev_name()

r = netcontrol.query("get_mac", { "ip": ip })
mac = r["mac"]
try:
mac = netcontrol.get_mac(ip)
except requests.HTTPError as e:
raise ValidationError(
_("Could not get MAC address.")
) from e

# Validate the MAC address
validate_mac(mac)

netcontrol.query("connect_user", { "mac": mac, "name": user.username })

mark = get_mark(user)
netcontrol.query("set_mark", { "mac": mac, "mark": mark })

logger.info(
"Connected device %s (owned by %s) at %s to the internet.",
mac,
user.username,
ip
)
try:
netcontrol.connect_user(mac, mark, user.username)
logger.info(
"Connected device %s (owned by %s) at %s to the internet.",
mac,
user.username,
ip
)
except requests.HTTPError as e:
raise ValidationError(
_("Could not connect user.")
) from e

try:
device = UserDevice.objects.create(mac=mac, name=name, user=user, ip=ip, mark=mark)
device.save()
return device
except Exception as e:
netcontrol.query("disconnect_user", { "mac": mac })
try:
netcontrol.disconnect_user(mac)
except requests.HTTPError as e:
raise ValidationError(
_("Could not disconnect user.")
) from e
raise ValidationError(
_("There was an error creating the device. Please try again.")
) from e
Expand All @@ -142,19 +169,28 @@ def edit_device(device: Device, mac, name, mark=None):
if name and name != device.name:
device.name = name
if mac and mac != device.mac:
validate_mac(mac)
# Disconnect the old MAC
netcontrol.query("disconnect_user", { "mac": device.mac })

# Connect the new MAC
netcontrol.query("connect", { "mac": mac, "name": device.name })
device.mac = mac
validate_mac(mac)
# Disconnect the old MAC
try:
netcontrol.disconnect_user(device.mac)
# Connect the new MAC
netcontrol.connect_user(mac, device.mark, device.name)
device.mac = mac
except requests.HTTPError as e:
raise ValidationError(
_("Could not connect user")
) from e
if mark and mark != device.mark:
# Check if the mark is valid
if mark not in [m["value"] for m in SETTINGS["marks"]]:
raise ValidationError(_("Invalid mark"))
device.mark = mark
netcontrol.query("set_mark", { "mac": device.mac, "mark": mark })
try:
netcontrol.set_mark(device.mac, mark)
except requests.HTTPError as e:
raise ValidationError(
_("Could not set mark")
) from e

try:
device.save()
Expand Down
Loading
Loading