Skip to content

Commit

Permalink
Merge pull request #6 from OthmanEmpire/develop
Browse files Browse the repository at this point in the history
Synchronising development branch with master branch
  • Loading branch information
othmanalikhan authored Oct 24, 2019
2 parents 7d28993 + b34b5ff commit c8d4989
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 133 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- sudo ./bin/install.sh
- ./test/requirements/requirements_linux.sh
script:
- bats ./test/test_deployment.sh
- sudo bats ./test/test_deployment.sh

### EXTRA CONFIGURATION

Expand Down
32 changes: 0 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,35 +110,3 @@ Acknowledgements
- Below are scripts that heavily inspired this project:
- Python implementation: http://praytimes.org/calculation/
- C++ implementation: http://3adly.blogspot.com/2010/07/prayer-times-calculations-pure-c-code.html


Roadmap
-------
1. [ ] LED for feedback.
2. [ ] Automatic initial setting up of config file.
2.1 [ ] Integration with Google maps to pickup longitude, latitude.
2.2 [ ] Integration with OS to pickup timezone.
3. [ ] GUI to control config
3.1 [ ] Real-time updating of prayer times based on config.json parameters.
4. [ ] Advanced ARPing
5. [ ] Add an uninstall script


TODO
----
- Study how input parameters (e.g. JD, LAT, LON) vary prayer times mathematically.
- Study domain and range restrictions of formulae
- Document the findings above in docstrings
- Update GIF to match new architecture
- Draw high-level diagram of operation
- Add TZ commands to sheet
- Fix arpspoofing happening infinitely
- Fix prayer time not scheduled after Isha (end of day)


Checklist
---------
- Unit/Integration Tests
- Deployment tests
- VM Arpspoofing test
- Real deployment test
42 changes: 26 additions & 16 deletions aroundtheclock/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import functools
import logging
import logging.config
import shlex
import subprocess

import schedule
Expand All @@ -15,16 +16,20 @@ def oneTimeJob(func):
Decorator that causes the given scheduled function to run only once.
As a bonus, it also logs the subsequent job to be ran.
NOTE: This decorator suppresses any results returned by the given function.
NOTE: This decorator suppresses any returned results by the given function.
:param func: function, the function to be wrapped.
:return: function, the wrapped function.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
logging.info("Next job at {}.".format(schedule.default_scheduler.next_run))
return schedule.CancelJob
func(*args, **kwargs) # Outside try-except to allow exception bubbling
try:
logging.info("Next job at {}.".format(schedule.jobs[1].next_run))
except IndexError:
logging.info("No next job scheduled!")
finally:
return schedule.CancelJob
return wrapper


Expand All @@ -43,17 +48,22 @@ def blockInternet(duration):
:return: Scheduler.CancelJob, making this function a one-off job (no repeat).
"""
# Fetch network parameters from OS for arp spoofing
p1 = subprocess.run(["ip", "route"], stdout=subprocess.PIPE)
GATEWAY = p1.stdout.split()[2].decode("ascii")
INTERFACE = p1.stdout.split()[4].decode("ascii")
logging.info("Found GW={}, INT={}".format(GATEWAY, INTERFACE))

# Arp spoof entire network for a limited duration
try:
logging.info("Blocking internet for {} minute(s)!".format(duration))
seconds = duration*60
subprocess.run(["timeout", str(seconds), "sudo", "arpspoof", "-i", INTERFACE, GATEWAY],
timeout=seconds)
except subprocess.TimeoutExpired:
logging.info("Block time over, unblocking internet now!")
# Send output to PIPE to store in buffer
cmdRoute = "ip route"
p1 = subprocess.run(shlex.split(cmdRoute), stdout=subprocess.PIPE)
GATEWAY = p1.stdout.split()[2].decode("ascii")
INTERFACE = p1.stdout.split()[4].decode("ascii")
logging.info("Found GW={}, INT={}".format(GATEWAY, INTERFACE))
except IndexError:
logging.exception("The output of 'ip route' is empty! It looks like"
"The OS networking service might need a restart!")
raise OSError("'ip route' failed to return output!")

# Arp spoof entire network for a limited duration
logging.info("Blocking internet for {} minute(s)!".format(duration))
seconds = int(duration * 60)
cmdBlock = "sudo aroundtheclock {} {} {}".format(INTERFACE, GATEWAY, seconds)
logging.info("Ran the following command to block: '{}'".format(cmdBlock))
subprocess.run(shlex.split(cmdBlock))
logging.info("Block time over, unblocking internet now!")
47 changes: 25 additions & 22 deletions aroundtheclock/led.py
Original file line number Diff line number Diff line change
@@ -1,59 +1,62 @@
"""
Responsible for controlling the Raspberry Pi pins.
Responsible for controlling the Raspberry Pi pins connected to an LED.
"""


import RPi.GPIO as GPIO
import time


LED_PIN = 18
import RPi.GPIO as GPIO


def initialisePi():
def initialisePi(pin: int):
"""
Initialises the PIN to be connected to the LED.
:return:
:param pin: The pin connected to the LED.
:return: RPi.GPIO.PWM, representing the pin connected to the LED.
"""
FREQUENCY = 1000

GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.setup(LED_PIN, GPIO.LOW)
GPIO.setup(pin, GPIO.OUT)
GPIO.setup(pin, GPIO.LOW)

led = GPIO.PWM(LED_PIN, 1000)
led = GPIO.PWM(pin, FREQUENCY)
led.start(0)
return led


def blinkLED(pin, pause):
def blinkLED(pin, speed: int):
"""
Causes the LED to gradually change brightness (0% -> 100% -> 0%).
:param pin:
:param pause: Integer, the number of seconds to
:return:
:param pin: RPi.GPIO.PWM, representing the pin connected to the LED.
:param speed: The number of seconds the LED takes to blink fully.
"""
DUTY_DELAY = 0.05
INTERVAL = 1
MIN_PERCENT = 0
MAX_PERCENT = 100
STEPS = (MAX_PERCENT - MIN_PERCENT) / INTERVAL
DUTY_DELAY = speed / (2 * STEPS)

print("Fire me up Scotty! (Pin 18)")

for percentage in range(0, 100, 5):
for percentage in range(MIN_PERCENT, MAX_PERCENT, INTERVAL):
pin.ChangeDutyCycle(percentage)
time.sleep(DUTY_DELAY)

for percentage in range(100, 0, -5):
for percentage in range(MAX_PERCENT, MIN_PERCENT, -INTERVAL):
pin.ChangeDutyCycle(percentage)
time.sleep(DUTY_DELAY)

print("Power is dry!")


def main():
"""
Entry point of the module.
"""
led = initialisePi()
blinkLED(led, 1)
LED_PIN = 18
led = initialisePi(LED_PIN)
blinkLED(led, 3)


if __name__ == "__main__":
main()

24 changes: 21 additions & 3 deletions aroundtheclock/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,21 @@
import json
import logging
import logging.config
import time
from pathlib import Path

import schedule

from block import blockInternet
from prayer import nextFivePrayers, printPrayerTimes, writePrayerTimes

# Try importing a module that uses RPi.GPIO library for Raspberry Pis.
# This would fail on Windows/Linux platforms (need Raspberry Pi hardware)
try:
import led
except (ImportError, RuntimeError):
led = None


PATH_ROOT = Path(__file__, "../../").absolute().resolve()


Expand All @@ -38,19 +45,30 @@ def main():
logger = logging.getLogger(__name__)
logger.info("Starting project AroundTheClock!")

# Initialising LED
if led:
pin = led.initialisePi(CONFIG["pin"])

######################################## SCHEDULING

# Schedule blocking times for prayers otherwise wait on existing jobs.
while True:
if schedule.default_scheduler.next_run:

if led:
blinkSpeed = schedule.default_scheduler.next_run - dt.datetime.now()
blinkSpeed = blinkSpeed.total_seconds() / 3600
if blinkSpeed < 0.5:
blinkSpeed = 0.5
led.blinkLED(pin, blinkSpeed)

schedule.run_pending()
time.sleep(1)
else:
FORMAT_SCHEDULE = "%H:%M"
FORMAT_PRINT = "%Y-%m-%d %H:%M"

# Computing prayer times
logger.info("Computing today's prayer times {}!".format(dt.date.today()))
logger.info("Computing next five prayers after {}!".format(dt.date.today()))
prayers = nextFivePrayers((CONFIG["longitude"], CONFIG["latitude"]),
CONFIG["timezone"],
CONFIG["fajr_isha"],
Expand Down
90 changes: 90 additions & 0 deletions aroundtheclock/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
Block internet connectivity via arp poisoning.
"""

import functools
import logging
import logging.config
import shlex
import subprocess
from subprocess import PIPE, Popen
from threading import Timer


import schedule


def oneTimeJob(func):
"""
Decorator that causes the given scheduled function to run only once.
As a bonus, it also logs the subsequent job to be ran.
NOTE: This decorator suppresses any results returned by the given function.
:param func: function, the function to be wrapped.
:return: function, the wrapped function.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
logging.info("Next job at {}.".format(schedule.jobs[1].next_run))
except IndexError:
# TODO: Test this execution flow!
logging.info("No next job scheduled!")
finally:
func(*args, **kwargs)
return schedule.CancelJob
return wrapper


@oneTimeJob
def blockInternet(duration):
"""
For the physical device running this script that is connected to a
network, arp poisons its default gateway on the network for all hosts,
thereby 'suspending' connectivity to WAN (internet) for the given duration.
Pre-requisites for this function to run properly:
1. Install arpspoof on OS: sudo apt-get install dsniff.
2. Run this function using an OS account with root privileges.
:param duration: Integer, the block duration in minutes.
:return: Scheduler.CancelJob, making this function a one-off job (no repeat).
"""
import time
# Fetch network parameters from OS for arp spoofing
print("GOING TO SLEEP!")

def noob():
print("STILL SLEEPING")
time.sleep(1)
p1 = subprocess.Popen(shlex.split("ping -t google.com"), shell=True)
# p1.communicate()

schedule.every().day.at("20:28").do(noob)
print("DONE!")

while True:
schedule.run_all()
print("WAITING")
time.sleep(1)

# Arp spoof entire network for a limited duration
# proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE)
# timer = Timer(seconds, proc.kill)
# logging.info("Blocking internet for {} seconds!".format(seconds))
# logging.info("Ran the following command to block: '{}'".format(cmd))
#
# try:
# timer.start()
# finally:
# logging.info("Block time over, unblocking internet now!")
# timer.cancel()


def main():
blockInternet(10)


if __name__ == "__main__":
main()
38 changes: 38 additions & 0 deletions bin/aroundtheclock
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/bin/bash

PROGRAM=$0

function usage() {
echo -e "\nCOMMAND: '$PROGRAM'"

echo -e "\nDESCRIPTION: A utility used by project_aroundtheclock to"
echo " temporarily block the internet. This command is invoked by"
echo " the project itself and shouldn't normally be used as a standalone."

echo -e "\nDEPENDENCIES: dnsniff"

echo -e "\nUSAGE: $PROGRAM <INTERFACE> <GATEWAY> <DURATION>"
echo " INTERFACE the network interface connected to the blocking network"
echo " GATEWAY the gateway that needs to be blocked temporarily"
echo " DURATION the duration is seconds to block internet"
exit 1
}

function blockInternet() {
INTERFACE="$1"
GATEWAY="$2"
DURATION="$3"

timeout "$DURATION" arpspoof -i "$INTERFACE" "$GATEWAY"
exit 0
}

if [ $# -eq 3 ]; then
INTERFACE="$1"
GATEWAY="$2"
DURATION="$3"

blockInternet "$INTERFACE" "$GATEWAY" "$DURATION"
else
usage
fi
Loading

0 comments on commit c8d4989

Please sign in to comment.