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 |
|---|---|
|
Loopback (for local testing) |
|
First Ethernet adapter |
|
Ethernet (predictable naming) |
|
First WiFi adapter |
|
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 |
|---|---|
|
Run with |
|
Same as above — socket creation failed due to permissions |
|
Interface name is wrong — check with |
|
Interface is down — bring it up with |
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
Getting Started — Complete V2X tutorial
Link Layer — Link Layer documentation
Docker Deployment — Deploy in Docker containers
Logging — Configure logging output