commit 4be9f67e63903a40e55bb70a690e8d21288d9df3 Author: Maxime Bloch Date: Tue Jan 21 03:27:35 2020 +0100 Initial commit diff --git a/.env b/.env new file mode 100644 index 0000000..4a5881f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +INTERFACE=wlp58s0 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8eb589e --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ + +venv/ +__pycache__/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dcc0cfa --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +#!make +include .env +export + +listener: + python listener.py +example-sender: + python python/sender.py \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec7d8f5 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Raw socket programming challenge + +How to send a pure ethernet packet? + +Let us know in your favorite programming language! + + +## What are we expecting? + +A layer 2 ethernet frame is constructed in the following way. + +``` +[Destination MAC: 6 bytes][Source MAC: 6 bytes][Protocol: 2 bytes][Message: ZeusWPI is de max!] +``` +For example +```python +\xff\xff\xff\xff\xff\xff \x12\xab\x34\xcd\x56 \x60\x00 \x5a\x45\x75\x53... + Broadcast Dest MAC | Source MAC | Protocol | Your binary message +``` + +Send that frame with the message `ZeusWPI is de max!` over the same network interface that the listener is listening on and it will confirm that you completed the challenge! + +You can then put your code in a folder with the name of the programming language providing instructions on how to run it. + + + +## How to +We are providing some code that will listen to any packets received. It will give you a confirmation if you completed the challenge. + +The only python package requirement is `netifaces`. +Install with the command `pip install --user netifaces` + +Create a file named `.env` with the following line `INTERFACE=YOUR_NETWORK_INTERFACE` + +* Run the listener with `sudo make listener` + +* We also provide an example client in python. Run it with `sudo make example-sender` \ No newline at end of file diff --git a/listener.py b/listener.py new file mode 100644 index 0000000..ce79f27 --- /dev/null +++ b/listener.py @@ -0,0 +1,64 @@ +import datetime +import socket +import sys +from typing import Optional + +from utils import mac_address, broadcast_address + + +class Frame: + + def __init__(self, + destination: bytes, + protocol: bytes, + message: str, + source: bytes = mac_address): + self.destination: bytes = destination + self.source: bytes = source + self.protocol: bytes = protocol + self.message: str = message + + +def decode_packet(packet_bytes: bytes) -> Optional[Frame]: + protocol: bytes = packet_bytes[12:14] + + if protocol != b"\x60\x00": + return None + + try: + destination = packet_bytes[0:6] + source = packet_bytes[6:12] + message = packet_bytes[14:].decode("utf-8").rstrip("\x00") + return Frame(destination=destination, + source=source, + protocol=protocol, + message=message) + except: + # print("[Error] Could not decode message.") + return None + + +if __name__ == "__main__": + + print("Starting listener...") + sys.stdout.flush() + + connection = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3)) + + while True: + byte_string, addr = connection.recvfrom(65536) + + frame = decode_packet(byte_string) + + if frame is not None \ + and (frame.destination == mac_address or frame.destination == broadcast_address): + dt_string = datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S") + print("----") + print("Revcd from {} to {} @{}".format(frame.source, frame.destination, dt_string)) + + if frame.message == "ZeusWPI is de max!": + print("Congratulations, packet received correctly!") + else: + print("Wrong message, please check that you are sending \"ZeusWPI is de max!\"") + + sys.stdout.flush() diff --git a/python/sender.py b/python/sender.py new file mode 100644 index 0000000..0ca3f3e --- /dev/null +++ b/python/sender.py @@ -0,0 +1,47 @@ +import datetime +from _socket import socket, AF_PACKET, SOCK_RAW +from time import sleep + +import utils + + +class Frame: + + def __init__(self, + destination: bytes, + protocol: bytes, + message: str, + source: bytes = utils.mac_address): + self.destination: bytes = destination + self.source: bytes = source + self.protocol: bytes = protocol + self.message: str = message + + +def send_packet(packet: Frame) -> bool: + sock = socket(AF_PACKET, SOCK_RAW) + + encoded = packet.message.encode() + sock.bind((utils.get_device_name(), 0)) + + """ + The way ethernet works is: + - 6 bytes indicating destination mac + - 6 bytes indicating source mac + - 2 bytes indicating the type + - maximum 1500 bytes of data to send + """ + data = (packet.destination + utils.mac_address + + packet.protocol + encoded) + dt_string = datetime.datetime.now().strftime("%d/%m/%Y %H:%M:%S") + print("Sending message to {} @{}: {}".format(packet.destination, dt_string, packet.message)) + sock.send(data) + return True + + +if __name__ == "__main__": + while True: + send_packet(Frame(destination=utils.broadcast_address, + protocol=b"\x60\x00", + message="ZeusWPI is de max!")) + sleep(1) diff --git a/python/utils.py b/python/utils.py new file mode 120000 index 0000000..50fbc6d --- /dev/null +++ b/python/utils.py @@ -0,0 +1 @@ +../utils.py \ No newline at end of file diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..2df29d4 --- /dev/null +++ b/utils.py @@ -0,0 +1,22 @@ +import os + +import netifaces + + +def get_device_name() -> str: + if 'INTERFACE' not in os.environ: + print("[Error] \"INTERFACE\" not found in your environment variables") + print(" Please specify your network interface") + exit(0) + + return os.environ['INTERFACE'] + + +def get_mac_address() -> bytes: + return bytes(list(map(lambda i: int(i, 16), netifaces.ifaddresses( + get_device_name())[netifaces.AF_LINK][0]["addr"].split(":")))) + + +mac_address = get_mac_address() + +broadcast_address = b'\xff\xff\xff\xff\xff\xff'