Ethernet Deployment with RawLinkLayer

Send and receive V2X messages over real Ethernet networks using FlexStack’s RawLinkLayer. This guide covers network interface selection, permissions, and troubleshooting.

Important

The RawLinkLayer only works on Linux-based operating systems (Ubuntu, Debian, etc.). It requires root privileges to access raw sockets.

How It Works

The RawLinkLayer sends V2X messages as raw Ethernet frames:

        flowchart LR
    subgraph "FlexStack"
        APP[Application]
        FAC[Facilities<br/>CAM/VAM/DENM]
        BTP[BTP Router]
        GN[GeoNetworking]
        LL[RawLinkLayer]
    end

    subgraph "Operating System"
        SOCK[Raw Socket]
        NIC[Network Interface<br/>eth0 / wlan0]
    end

    subgraph "Network"
        ETH[Ethernet<br/>Frames]
    end

    APP --> FAC --> BTP --> GN --> LL
    LL --> SOCK --> NIC --> ETH

    style LL fill:#e3f2fd,stroke:#1565c0
    style NIC fill:#fff3e0,stroke:#e65100
    

Quick Start

Step 1: Find Your Network Interface

List available network interfaces:

ip link show

Example output:

1: lo: <LOOPBACK,UP,LOWER_UP> ...
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...

Or use the routing table to find the default interface:

route -n | head -3

Example output:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.1.1     0.0.0.0         UG    100    0        0 eth0

The Iface column shows your active network interface.

Common Interface Names:

Interface

Description

lo

Loopback (for local testing)

eth0

First Ethernet adapter

enp0s3

Ethernet (predictable naming)

wlan0

First WiFi adapter

wlp2s0

WiFi (predictable naming)

Step 2: Create Your Application

 1#!/usr/bin/env python3
 2"""
 3RawLinkLayer Ethernet Example
 4
 5Sends VAMs over a real network interface.
 6Run with: sudo python ethernet_example.py
 7"""
 8
 9import logging
10import time
11
12from flexstack.linklayer.raw_link_layer import RawLinkLayer
13from flexstack.geonet.router import Router as GNRouter
14from flexstack.geonet.mib import MIB
15from flexstack.geonet.gn_address import GNAddress, M, ST, MID
16from flexstack.btp.router import Router as BTPRouter
17from flexstack.utils.static_location_service import ThreadStaticLocationService
18from flexstack.facilities.vru_awareness_service.vru_awareness_service import (
19    VRUAwarenessService,
20)
21from flexstack.facilities.vru_awareness_service.vam_transmission_management import (
22    DeviceDataProvider,
23)
24
25logging.basicConfig(level=logging.INFO)
26
27# ============ Configuration ============ #
28
29INTERFACE = "eth0"                        # ← Change this to your interface
30MAC_ADDRESS = b"\xaa\xbb\xcc\x11\x22\x33" # ← Your MAC address
31STATION_ID = 1
32POSITION = [41.386931, 2.112104]          # Barcelona
33
34
35def main():
36    # GeoNetworking
37    mib = MIB(
38        itsGnLocalGnAddr=GNAddress(
39            m=M.GN_MULTICAST,
40            st=ST.CYCLIST,
41            mid=MID(MAC_ADDRESS),
42        ),
43    )
44    gn_router = GNRouter(mib=mib, sign_service=None)
45
46    # Link Layer - connects to real network!
47    link_layer = RawLinkLayer(
48        iface=INTERFACE,
49        mac_address=MAC_ADDRESS,
50        receive_callback=gn_router.gn_data_indicate,
51    )
52    gn_router.link_layer = link_layer
53
54    # BTP
55    btp_router = BTPRouter(gn_router)
56    gn_router.register_indication_callback(btp_router.btp_data_indication)
57
58    # Location Service
59    location_service = ThreadStaticLocationService(
60        period=1000,
61        latitude=POSITION[0],
62        longitude=POSITION[1],
63    )
64    location_service.add_callback(gn_router.refresh_ego_position_vector)
65
66    # VRU Awareness Service
67    device_data = DeviceDataProvider(
68        station_id=STATION_ID,
69        station_type=2,  # Cyclist
70    )
71    vru_service = VRUAwarenessService(
72        btp_router=btp_router,
73        device_data_provider=device_data,
74    )
75    location_service.add_callback(
76        vru_service.vam_transmission_management.location_service_callback
77    )
78
79    print(f"✅ Sending VAMs on interface: {INTERFACE}")
80    print(f"📡 MAC Address: {MAC_ADDRESS.hex(':')}")
81    print("Press Ctrl+C to stop\n")
82
83    try:
84        location_service.location_service_thread.join()
85    except KeyboardInterrupt:
86        print("\n👋 Stopping...")
87
88    location_service.stop_event.set()
89    link_layer.sock.close()
90
91
92if __name__ == "__main__":
93    main()

Step 3: Run with Root Privileges

Raw sockets require root access. You have two options:

Option 1: Use sudo directly

sudo python ethernet_example.py

Or if using a virtual environment:

sudo env PATH=$PATH python ethernet_example.py

Option 2: Switch to root user

sudo su
source venv/bin/activate
python ethernet_example.py

Common Errors

Error

Solution

PermissionError: [Errno 1] Operation not permitted

Run with sudo — raw sockets need root

AttributeError: 'RawLinkLayer' object has no attribute 'sock'

Same as above — socket creation failed due to permissions

OSError: [Errno 19] No such device

Interface name is wrong — check with ip link show

OSError: [Errno 99] Cannot assign requested address

Interface is down — bring it up with sudo ip link set eth0 up


Testing Locally

For development, use the loopback interface (lo):

link_layer = RawLinkLayer(
    iface="lo",  # Loopback for local testing
    mac_address=MAC_ADDRESS,
    receive_callback=gn_router.gn_data_indicate,
)

This allows you to test without a real network, though messages won’t leave your machine.


Two-Station Test

Test V2X communication between two terminals on the same machine:

Terminal 1 — Station A:

sudo python station.py --station-id 1 --interface eth0

Terminal 2 — Station B:

sudo python station.py --station-id 2 --interface eth0

Both stations should see each other’s messages in the logs!

        sequenceDiagram
    participant A as Station A<br/>(Terminal 1)
    participant NET as eth0<br/>Network
    participant B as Station B<br/>(Terminal 2)

    A->>NET: VAM (Station 1)
    NET->>B: Receive VAM
    B->>NET: VAM (Station 2)
    NET->>A: Receive VAM

    Note over A,B: Both stations see each other
    

Network Capture

Use Wireshark or tcpdump to inspect V2X packets:

# Capture GeoNetworking packets (EtherType 0x8947)
sudo tcpdump -i eth0 ether proto 0x8947 -v

Example output:

14:32:15.123456 aa:bb:cc:11:22:33 > ff:ff:ff:ff:ff:ff, ethertype Unknown (0x8947), length 128
14:32:15.623456 aa:bb:cc:11:22:34 > ff:ff:ff:ff:ff:ff, ethertype Unknown (0x8947), length 132

Or capture to a file for Wireshark:

sudo tcpdump -i eth0 ether proto 0x8947 -w v2x_capture.pcap

MAC Address Configuration

You can use any MAC address, but for proper operation:

Random locally-administered MAC:

import random

def generate_random_mac() -> bytes:
    octets = [random.randint(0x00, 0xFF) for _ in range(6)]
    # Set locally administered bit, clear multicast bit
    octets[0] = (octets[0] & 0xFE) | 0x02
    return bytes(octets)

MAC_ADDRESS = generate_random_mac()

Parse from string:

def parse_mac(mac_str: str) -> bytes:
    """Convert 'aa:bb:cc:dd:ee:ff' to bytes."""
    return bytes(int(x, 16) for x in mac_str.split(":"))

MAC_ADDRESS = parse_mac("aa:bb:cc:11:22:33")

Use your real MAC:

# Find your MAC address
ip link show eth0 | grep ether
link/ether 00:1a:2b:3c:4d:5e brd ff:ff:ff:ff:ff:ff

Complete Example

Here’s a complete station with command-line arguments:

  1#!/usr/bin/env python3
  2"""
  3FlexStack Ethernet Station
  4
  5Usage:
  6    sudo python station.py --interface eth0 --station-id 1
  7"""
  8
  9import argparse
 10import logging
 11import random
 12import time
 13
 14from flexstack.linklayer.raw_link_layer import RawLinkLayer
 15from flexstack.geonet.router import Router as GNRouter
 16from flexstack.geonet.mib import MIB
 17from flexstack.geonet.gn_address import GNAddress, M, ST, MID
 18from flexstack.btp.router import Router as BTPRouter
 19from flexstack.utils.static_location_service import ThreadStaticLocationService
 20from flexstack.facilities.vru_awareness_service.vru_awareness_service import (
 21    VRUAwarenessService,
 22)
 23from flexstack.facilities.vru_awareness_service.vam_transmission_management import (
 24    DeviceDataProvider,
 25)
 26
 27
 28def parse_mac(mac_str: str) -> bytes:
 29    return bytes(int(x, 16) for x in mac_str.split(":"))
 30
 31
 32def generate_random_mac() -> bytes:
 33    octets = [random.randint(0x00, 0xFF) for _ in range(6)]
 34    octets[0] = (octets[0] & 0xFE) | 0x02
 35    return bytes(octets)
 36
 37
 38def main():
 39    parser = argparse.ArgumentParser(description="FlexStack Ethernet Station")
 40    parser.add_argument(
 41        "--interface", "-i",
 42        type=str,
 43        default="eth0",
 44        help="Network interface (e.g., eth0, wlan0, lo)",
 45    )
 46    parser.add_argument(
 47        "--station-id", "-s",
 48        type=int,
 49        default=random.randint(1, 2147483647),
 50        help="Station ID (default: random)",
 51    )
 52    parser.add_argument(
 53        "--mac-address", "-m",
 54        type=str,
 55        default=None,
 56        help="MAC address (e.g., aa:bb:cc:dd:ee:ff)",
 57    )
 58    parser.add_argument(
 59        "--latitude",
 60        type=float,
 61        default=41.386931,
 62        help="Latitude in degrees",
 63    )
 64    parser.add_argument(
 65        "--longitude",
 66        type=float,
 67        default=2.112104,
 68        help="Longitude in degrees",
 69    )
 70    parser.add_argument(
 71        "--debug",
 72        action="store_true",
 73        help="Enable debug logging",
 74    )
 75    args = parser.parse_args()
 76
 77    # Setup logging
 78    logging.basicConfig(
 79        level=logging.DEBUG if args.debug else logging.INFO,
 80        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
 81        datefmt="%H:%M:%S",
 82    )
 83    logger = logging.getLogger("station")
 84
 85    # MAC address
 86    if args.mac_address:
 87        mac = parse_mac(args.mac_address)
 88    else:
 89        mac = generate_random_mac()
 90
 91    logger.info(f"🚀 Starting station {args.station_id}")
 92    logger.info(f"📡 Interface: {args.interface}")
 93    logger.info(f"🔗 MAC: {mac.hex(':')}")
 94    logger.info(f"📍 Position: {args.latitude}, {args.longitude}")
 95
 96    # GeoNetworking
 97    mib = MIB(
 98        itsGnLocalGnAddr=GNAddress(
 99            m=M.GN_MULTICAST,
100            st=ST.CYCLIST,
101            mid=MID(mac),
102        ),
103    )
104    gn_router = GNRouter(mib=mib, sign_service=None)
105
106    # Link Layer
107    try:
108        link_layer = RawLinkLayer(
109            iface=args.interface,
110            mac_address=mac,
111            receive_callback=gn_router.gn_data_indicate,
112        )
113    except PermissionError:
114        logger.error("❌ Permission denied! Run with sudo.")
115        return
116    except OSError as e:
117        logger.error(f"❌ Failed to open interface: {e}")
118        return
119
120    gn_router.link_layer = link_layer
121
122    # BTP
123    btp_router = BTPRouter(gn_router)
124    gn_router.register_indication_callback(btp_router.btp_data_indication)
125
126    # Location Service
127    location_service = ThreadStaticLocationService(
128        period=1000,
129        latitude=args.latitude,
130        longitude=args.longitude,
131    )
132    location_service.add_callback(gn_router.refresh_ego_position_vector)
133
134    # VRU Awareness Service
135    device_data = DeviceDataProvider(
136        station_id=args.station_id,
137        station_type=2,
138    )
139    vru_service = VRUAwarenessService(
140        btp_router=btp_router,
141        device_data_provider=device_data,
142    )
143    location_service.add_callback(
144        vru_service.vam_transmission_management.location_service_callback
145    )
146
147    logger.info("✅ Station running! Press Ctrl+C to stop.\n")
148
149    try:
150        while True:
151            time.sleep(1)
152    except KeyboardInterrupt:
153        logger.info("\n👋 Shutting down...")
154
155    location_service.stop_event.set()
156    location_service.location_service_thread.join()
157    link_layer.sock.close()
158    logger.info("Goodbye!")
159
160
161if __name__ == "__main__":
162    main()

Usage:

# Basic usage
sudo python station.py --interface eth0

# With custom settings
sudo python station.py \
    --interface wlan0 \
    --station-id 42 \
    --mac-address aa:bb:cc:dd:ee:ff \
    --latitude 48.8566 \
    --longitude 2.3522 \
    --debug

See Also