mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-10-29 17:42:41 +00:00
WIFI-14018: Allow option 82 DHCP fields to be transparently injected into client DHCP requests
1. Added userspace application udhcpinject to inject DHCP option 82 transparently
2. Added README.md for example usage
Signed-off-by: alex18_huang <alex18_huang@accton.com>
new file: feeds/ucentral/udhcpinject/Makefile
new file: feeds/ucentral/udhcpinject/README.md
new file: feeds/ucentral/udhcpinject/files/etc/config/dhcpinject
new file: feeds/ucentral/udhcpinject/files/etc/init.d/dhcpinject
new file: feeds/ucentral/udhcpinject/src/Makefile
new file: feeds/ucentral/udhcpinject/src/udhcpinject.c
new file: feeds/ucentral/udhcpinject/src/udhcpinject.h
This commit is contained in:
44
feeds/ucentral/udhcpinject/Makefile
Normal file
44
feeds/ucentral/udhcpinject/Makefile
Normal file
@@ -0,0 +1,44 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=udhcpinject
|
||||
PKG_VERSION:=1.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/udhcpinject
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
TITLE:=An agent to inject DHCP option
|
||||
DEPENDS:=+libpcap +kmod-ifb +tc
|
||||
MAINTAINER:=kmk <alex18_huang@accton.com>
|
||||
endef
|
||||
|
||||
define Package/udhcpinject/description
|
||||
A utility to insert DHCP option 82 transparently into DHCP Discover packets.
|
||||
The format is as follows:
|
||||
Option 82: (82) Agent Information
|
||||
Option: (1) Agent Circuit ID
|
||||
Agent Circuit ID: BSSID:ESSID
|
||||
Option: (2) Agent Remote ID
|
||||
Agent Remote ID: AP Hostname
|
||||
endef
|
||||
|
||||
define Build/Prepare
|
||||
mkdir -p $(PKG_BUILD_DIR)
|
||||
$(CP) ./src/* $(PKG_BUILD_DIR)/
|
||||
endef
|
||||
|
||||
define Package/udhcpinject/conffiles
|
||||
/etc/config/dhcpinject
|
||||
endef
|
||||
|
||||
define Package/udhcpinject/install
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_BUILD_DIR)/udhcpinject $(1)/usr/bin/
|
||||
$(CP) ./files/* $(1)/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,udhcpinject))
|
||||
107
feeds/ucentral/udhcpinject/README.md
Normal file
107
feeds/ucentral/udhcpinject/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# DHCP Option82 injector
|
||||
inject DHCP option 82 transparently on TIP AP
|
||||
|
||||
## Format
|
||||
```
|
||||
Option 82: (82) Agent Information
|
||||
Option: (1) Agent Circuit ID
|
||||
Agent Circuit ID: BSSID:ESSID
|
||||
Option: (2) Agent Remote ID
|
||||
Agent Remote ID: AP Hostname
|
||||
```
|
||||
|
||||
## Usage
|
||||
add "dhcpinject" under ssid->services
|
||||
```json
|
||||
{
|
||||
"interfaces": [
|
||||
{
|
||||
"ethernet": [
|
||||
{
|
||||
"select-ports": [
|
||||
"WAN*"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ipv4": {
|
||||
"addressing": "dynamic"
|
||||
},
|
||||
"name": "WAN",
|
||||
"role": "upstream",
|
||||
"services": [
|
||||
"ssh",
|
||||
"lldp",
|
||||
"dhcp-snooping",
|
||||
"http"
|
||||
],
|
||||
"ssids": [
|
||||
{
|
||||
"bss-mode": "ap",
|
||||
"encryption": {
|
||||
"ieee80211w": "optional",
|
||||
"key": "ERC981206",
|
||||
"proto": "psk2"
|
||||
},
|
||||
"name": "EAP101-ERICHI",
|
||||
"services": [
|
||||
"wifi-frames",
|
||||
"dhcpinject" <-- devices connected to this ssid will trigger this application
|
||||
],
|
||||
"wifi-bands": [
|
||||
"2G",
|
||||
"5G"
|
||||
]
|
||||
},
|
||||
{
|
||||
"bss-mode": "ap",
|
||||
"encryption": {
|
||||
"ieee80211w": "optional",
|
||||
"key": "AKH981016",
|
||||
"proto": "psk2"
|
||||
},
|
||||
"name": "EAP101-AKIHO",
|
||||
"services": [
|
||||
"wifi-frames", <-- devices connected to this ssid will NOT trigger this application
|
||||
],
|
||||
"wifi-bands": [
|
||||
"2G",
|
||||
"5G"
|
||||
]
|
||||
},
|
||||
{
|
||||
"bss-mode": "ap",
|
||||
"encryption": {
|
||||
"ieee80211w": "optional",
|
||||
"key": "DMY971128",
|
||||
"proto": "psk2"
|
||||
},
|
||||
"name": "EAP101-DAMAYU",
|
||||
"services": [
|
||||
"wifi-frames",
|
||||
"dhcpinject" <-- devices connected to this ssid will trigger this application
|
||||
],
|
||||
"wifi-bands": [
|
||||
"2G"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"dhcp-inject": {
|
||||
"select-ports": [
|
||||
"WAN*" <-- Will fallback to eth0 if no ports are provided
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Workflow
|
||||
Suppose SSID EAP101-ERICHI binds to iface wlan0
|
||||
|
||||
- Application read SSIDs and uplink ports from env variable `$SSIDs` and `$PORTs` (generated by script `/etc/init.d/udhcpinject`)
|
||||
- Apply `tc` on `wlan0` to redirect DHCP Discover/Request to iface `ifb-inject`
|
||||
- Application listen on interface `ifb-inject` for incoming DHCP packets
|
||||
- On packets arrive, application identify the src interface by vlan tag and insert corresponding attribute
|
||||
- Forward packet to up0v0
|
||||
7
feeds/ucentral/udhcpinject/files/etc/config/dhcpinject
Normal file
7
feeds/ucentral/udhcpinject/files/etc/config/dhcpinject
Normal file
@@ -0,0 +1,7 @@
|
||||
# config device 'uplink'
|
||||
# list port 'eth0'
|
||||
#
|
||||
# config ssids 'ssids'
|
||||
# list ssid 'EAP101-ERICHI'
|
||||
# list ssid 'EAP101-AKIHO'
|
||||
# list ssid 'EAP101-DAMAYU'
|
||||
73
feeds/ucentral/udhcpinject/files/etc/init.d/dhcpinject
Executable file
73
feeds/ucentral/udhcpinject/files/etc/init.d/dhcpinject
Executable file
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
|
||||
USE_PROCD=1
|
||||
|
||||
START=95
|
||||
STOP=10
|
||||
|
||||
SERVICE_NAME="dhcpinject"
|
||||
PROG=/usr/bin/udhcpinject
|
||||
|
||||
start_service() {
|
||||
local ssid_list=""
|
||||
local ssids=""
|
||||
local ports=""
|
||||
|
||||
# Function to process each ssid
|
||||
append_ssid() {
|
||||
local value="$1"
|
||||
if [ -n "$ssids" ]; then
|
||||
ssids="$ssids,$value"
|
||||
else
|
||||
ssids="$value"
|
||||
fi
|
||||
}
|
||||
|
||||
append_port() {
|
||||
local value="$1"
|
||||
if [ -n "$ports" ]; then
|
||||
ports="$ports,$value"
|
||||
else
|
||||
ports="$value"
|
||||
fi
|
||||
}
|
||||
|
||||
# Load the dhcpinject config
|
||||
config_load dhcpinject
|
||||
|
||||
# Get the list of SSIDs
|
||||
config_list_foreach ssids ssid append_ssid
|
||||
|
||||
# Get the list of ports
|
||||
config_list_foreach uplink port append_port
|
||||
|
||||
# Fallback to eth0 if no ports are specified
|
||||
if [ -z "$ports" ]; then
|
||||
ports="eth0"
|
||||
fi
|
||||
|
||||
# Optional: Log or echo for debugging
|
||||
logger -t dhcp_inject "Generated SSIDs=$ssids, Uplink=$ports"
|
||||
|
||||
procd_open_instance "$SERVICE_NAME"
|
||||
procd_set_param command $PROG
|
||||
procd_set_param env SSIDs="$ssids" PORTs="$ports"
|
||||
procd_set_param respawn 3600 10 10
|
||||
procd_set_param file /etc/config/dhcpinject
|
||||
procd_set_param reload_signal SIGHUP
|
||||
procd_close_instance
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
procd_kill $SERVICE_NAME
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
procd_send_signal $SERVICE_NAME
|
||||
}
|
||||
|
||||
restart_service() {
|
||||
stop
|
||||
sleep 1
|
||||
start
|
||||
}
|
||||
16
feeds/ucentral/udhcpinject/src/Makefile
Normal file
16
feeds/ucentral/udhcpinject/src/Makefile
Normal file
@@ -0,0 +1,16 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
TARGET_CFLAGS += -Wall
|
||||
|
||||
obj-y := udhcpinject.o
|
||||
|
||||
all: udhcpinject
|
||||
|
||||
udhcpinject: $(obj-y)
|
||||
$(CC) $(LDFLAGS) -lpcap -o $@ $(obj-y)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) $(TARGET_CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f *.o udhcpinject
|
||||
626
feeds/ucentral/udhcpinject/src/udhcpinject.c
Normal file
626
feeds/ucentral/udhcpinject/src/udhcpinject.c
Normal file
@@ -0,0 +1,626 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <linux/if_vlan.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/udp.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <pcap.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "udhcpinject.h"
|
||||
|
||||
#define MAX_INTERFACES 48
|
||||
#define MAX_PORTS 8
|
||||
|
||||
// Global variables
|
||||
struct iface_info *iface_map = NULL;
|
||||
static struct port_info *ports = NULL;
|
||||
int iface_count = 0;
|
||||
int port_count = 0;
|
||||
static pcap_t *handle = NULL;
|
||||
static char *provided_ssids = NULL;
|
||||
static char *provided_ports = NULL;
|
||||
|
||||
// Function to cleanup tc rules
|
||||
void cleanup_tc() {
|
||||
char cmd[1024];
|
||||
for (int i = 0; i < iface_count; i++) {
|
||||
snprintf(cmd, sizeof(cmd), "tc filter del dev %s ingress 2>/dev/null",
|
||||
iface_map[i].iface);
|
||||
system(cmd);
|
||||
snprintf(cmd, sizeof(cmd), "tc qdisc del dev %s ingress 2>/dev/null",
|
||||
iface_map[i].iface);
|
||||
system(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup function
|
||||
void cleanup() {
|
||||
syslog(LOG_INFO, "Cleaning up resources...\n");
|
||||
cleanup_tc();
|
||||
|
||||
if (handle) {
|
||||
pcap_close(handle);
|
||||
handle = NULL;
|
||||
}
|
||||
if (ports) {
|
||||
for (int i = 0; i < port_count; i++) {
|
||||
if (ports[i].sock >= 0) {
|
||||
close(ports[i].sock);
|
||||
}
|
||||
}
|
||||
free(ports);
|
||||
ports = NULL;
|
||||
port_count = 0;
|
||||
}
|
||||
if (iface_map) {
|
||||
free(iface_map);
|
||||
iface_map = NULL;
|
||||
iface_count = 0;
|
||||
}
|
||||
if (provided_ssids) {
|
||||
free(provided_ssids);
|
||||
provided_ssids = NULL;
|
||||
}
|
||||
if (provided_ports) {
|
||||
free(provided_ports);
|
||||
provided_ports = NULL;
|
||||
}
|
||||
syslog(LOG_INFO, "Cleanup complete.\n");
|
||||
}
|
||||
|
||||
// Function to parse SSIDs and populate iface_map
|
||||
int parse_ssids(const char *ssids) {
|
||||
if (iface_map) {
|
||||
free(iface_map);
|
||||
iface_map = NULL;
|
||||
iface_count = 0;
|
||||
}
|
||||
|
||||
// Create a set of provided SSIDs for efficient lookup
|
||||
char ssids_copy[256];
|
||||
strncpy(ssids_copy, ssids, sizeof(ssids_copy) - 1);
|
||||
ssids_copy[sizeof(ssids_copy) - 1] = '\0';
|
||||
|
||||
// Count number of SSIDs for allocation
|
||||
int ssid_count = 1; // Start at 1 for first SSID
|
||||
for (int i = 0; ssids_copy[i]; i++) {
|
||||
if (ssids_copy[i] == ',')
|
||||
ssid_count++;
|
||||
}
|
||||
|
||||
char **ssid_set = malloc(ssid_count * sizeof(char *));
|
||||
if (!ssid_set) {
|
||||
syslog(LOG_ERR, "Failed to allocate memory for SSID set\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ssid_idx = 0;
|
||||
char *token = strtok(ssids_copy, ",");
|
||||
while (token) {
|
||||
ssid_set[ssid_idx++] = token;
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
|
||||
// Execute iwinfo command and capture output
|
||||
FILE *pipe = popen("iwinfo | grep wlan -A1 | grep -v \"^--\" | tr -d '\"' "
|
||||
"| awk '/wlan/ {name=$1; essid=$3} /Access Point/ "
|
||||
"{print name \"=\" essid \",\" $3}' | tr -d ':'",
|
||||
"r");
|
||||
if (!pipe) {
|
||||
syslog(LOG_ERR, "Failed to execute iwinfo command\n");
|
||||
free(ssid_set);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char line[256];
|
||||
while (fgets(line, sizeof(line), pipe) != NULL) {
|
||||
// Remove trailing newline
|
||||
line[strcspn(line, "\n")] = 0;
|
||||
|
||||
// Parse line format: wlanX=SSID,BSSID
|
||||
char *iface = strtok(line, "=");
|
||||
char *rest = strtok(NULL, "=");
|
||||
if (!iface || !rest)
|
||||
continue;
|
||||
|
||||
char *essid = strtok(rest, ",");
|
||||
char *bssid = strtok(NULL, ",");
|
||||
if (!essid || !bssid)
|
||||
continue;
|
||||
|
||||
// Check if this SSID is in our provided set
|
||||
int match = 0;
|
||||
for (int i = 0; i < ssid_idx; i++) {
|
||||
if (strcmp(essid, ssid_set[i]) == 0) {
|
||||
match = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match)
|
||||
continue;
|
||||
|
||||
// Add matching interface to iface_map
|
||||
if (iface_count >= MAX_INTERFACES) {
|
||||
syslog(LOG_ERR, "Too many matching interfaces, max is %d\n",
|
||||
MAX_INTERFACES);
|
||||
pclose(pipe);
|
||||
free(ssid_set);
|
||||
return -1;
|
||||
}
|
||||
|
||||
iface_map =
|
||||
realloc(iface_map, (iface_count + 1) * sizeof(struct iface_info));
|
||||
if (!iface_map) {
|
||||
syslog(LOG_ERR, "Failed to reallocate iface_map\n");
|
||||
pclose(pipe);
|
||||
free(ssid_set);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct iface_info *info = &iface_map[iface_count];
|
||||
info->serial = iface_count + 1;
|
||||
|
||||
strncpy(info->iface, iface, LEN_IFACE);
|
||||
info->iface[LEN_IFACE] = '\0';
|
||||
|
||||
strncpy(info->essid, essid, LEN_ESSID);
|
||||
info->essid[LEN_ESSID] = '\0';
|
||||
|
||||
strncpy(info->bssid, bssid, LEN_BSSID);
|
||||
info->bssid[LEN_BSSID] = '\0';
|
||||
|
||||
iface_count++;
|
||||
}
|
||||
|
||||
int pipe_status = pclose(pipe);
|
||||
if (pipe_status == -1) {
|
||||
syslog(LOG_ERR, "Error closing iwinfo pipe: %s\n", strerror(errno));
|
||||
}
|
||||
|
||||
free(ssid_set);
|
||||
|
||||
if (iface_count == 0) {
|
||||
syslog(LOG_ERR, "No matching interfaces found for provided SSIDs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
syslog(LOG_INFO, "Found %d matching interfaces\n", iface_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_ports(const char *port_list) {
|
||||
if (ports) {
|
||||
for (int i = 0; i < port_count; i++) {
|
||||
if (ports[i].sock >= 0) {
|
||||
close(ports[i].sock);
|
||||
}
|
||||
}
|
||||
free(ports);
|
||||
ports = NULL;
|
||||
port_count = 0;
|
||||
}
|
||||
|
||||
char ports_copy[256];
|
||||
strncpy(ports_copy, port_list, sizeof(ports_copy) - 1);
|
||||
ports_copy[sizeof(ports_copy) - 1] = '\0';
|
||||
|
||||
port_count = 1;
|
||||
for (int i = 0; ports_copy[i]; i++) {
|
||||
if (ports_copy[i] == ',') {
|
||||
port_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (port_count > MAX_PORTS) {
|
||||
syslog(LOG_ERR, "Too many ports specified, maximum is %d\n", MAX_PORTS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ports = calloc(port_count, sizeof(struct port_info));
|
||||
if (!ports) {
|
||||
syslog(LOG_ERR, "Failed to allocate memory for ports\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *token = strtok(ports_copy, ",");
|
||||
int idx = 0;
|
||||
while (token && idx < port_count) {
|
||||
strncpy(ports[idx].name, token, LEN_IFACE);
|
||||
ports[idx].name[LEN_IFACE] = '\0';
|
||||
|
||||
ports[idx].sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
|
||||
if (ports[idx].sock < 0) {
|
||||
syslog(LOG_ERR, "Socket creation failed for %s: %s\n",
|
||||
ports[idx].name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ports[idx].ifindex = if_nametoindex(ports[idx].name);
|
||||
if (ports[idx].ifindex == 0) {
|
||||
syslog(LOG_ERR, "Failed to get interface index for %s: %s\n",
|
||||
ports[idx].name, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
token = strtok(NULL, ",");
|
||||
idx++;
|
||||
}
|
||||
|
||||
syslog(LOG_INFO, "Configured %d ports for forwarding\n", port_count);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Function to setup tc rules (same as before but using iface_map)
|
||||
int setup_tc() {
|
||||
char cmd[1024];
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "ip link show ifb-inject >/dev/null 2>&1");
|
||||
if (system(cmd) != 0) {
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"ip link add name ifb-inject type ifb && ip link set "
|
||||
"ifb-inject up");
|
||||
if (system(cmd) != 0) {
|
||||
syslog(LOG_ERR, "Failed to setup ifb-inject\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < iface_count; i++) {
|
||||
snprintf(cmd, sizeof(cmd), "tc qdisc show dev %s ingress 2>/dev/null 1>2",
|
||||
iface_map[i].iface);
|
||||
if (system(cmd) == 0) {
|
||||
syslog(LOG_INFO, "Ingress qdisc already exists for %s\n", iface_map[i].iface);
|
||||
}
|
||||
else {
|
||||
snprintf(cmd, sizeof(cmd), "tc qdisc add dev %s ingress 2>/dev/null",
|
||||
iface_map[i].iface);
|
||||
if (system(cmd) != 0) {
|
||||
syslog(LOG_ERR, "Failed to add qdisc for %s\n", iface_map[i].iface);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"tc filter add dev %s ingress protocol ip u32 "
|
||||
"match ip protocol 17 0xff "
|
||||
"match u16 0x0044 0xffff at 20 "
|
||||
"match u16 0x0043 0xffff at 22 "
|
||||
"match u8 0x01 0xff at 28 "
|
||||
"action vlan push id %d pipe "
|
||||
"action mirred egress mirror dev ifb-inject pipe "
|
||||
"action drop",
|
||||
iface_map[i].iface, iface_map[i].serial);
|
||||
if (system(cmd) != 0) {
|
||||
syslog(LOG_ERR, "Failed to setup tc for %s\n", iface_map[i].iface);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Signal handler
|
||||
void signal_handler(int sig) {
|
||||
if (sig == SIGTERM) {
|
||||
syslog(LOG_INFO, "Received SIGTERM, cleaning up...\n");
|
||||
cleanup();
|
||||
exit(0);
|
||||
} else if (sig == SIGHUP) {
|
||||
syslog(LOG_INFO, "Received reload signal, reconfiguring...\n");
|
||||
|
||||
// Clean up existing resources
|
||||
cleanup_tc();
|
||||
|
||||
// Free old SSIDs and get new ones
|
||||
if (provided_ssids) {
|
||||
free(provided_ssids);
|
||||
}
|
||||
provided_ssids = getenv("SSIDs");
|
||||
if (!provided_ssids) {
|
||||
syslog(LOG_ERR, "No SSIDs provided on reload\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload SSIDs
|
||||
if (parse_ssids(provided_ssids) != 0) {
|
||||
syslog(LOG_ERR, "Failed to reload SSIDs configuration\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Free old ports and get new ones
|
||||
if (provided_ports) {
|
||||
free(provided_ports);
|
||||
}
|
||||
provided_ports = getenv("PORTs");
|
||||
if (!provided_ports) {
|
||||
syslog(LOG_ERR, "No PORTs provided on reload\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Close existing sockets and reopen with new config
|
||||
if (ports) {
|
||||
for (int i = 0; i < port_count; i++) {
|
||||
if (ports[i].sock >= 0) {
|
||||
close(ports[i].sock);
|
||||
}
|
||||
}
|
||||
free(ports);
|
||||
ports = NULL;
|
||||
port_count = 0;
|
||||
}
|
||||
|
||||
// Reload ports
|
||||
if (parse_ports(provided_ports) != 0) {
|
||||
syslog(LOG_ERR, "Failed to reload ports configuration\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Reapply tc rules
|
||||
if (setup_tc() == 0) {
|
||||
syslog(LOG_INFO, "Reloaded with SSIDs: %s and Ports: %s\n",
|
||||
provided_ssids, provided_ports);
|
||||
} else {
|
||||
syslog(LOG_ERR, "Failed to reload tc configuration\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *get_hostname() {
|
||||
static char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) != 0) {
|
||||
strcpy(hostname, "unknown");
|
||||
}
|
||||
return hostname;
|
||||
}
|
||||
|
||||
struct iface_info *find_iface_info_by_vlan(int vlan_id) {
|
||||
for (int i = 0; i < iface_count; i++) {
|
||||
if (iface_map[i].serial == vlan_id) {
|
||||
return &iface_map[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void process_packet(unsigned char *user, const struct pcap_pkthdr *header,
|
||||
const unsigned char *packet) {
|
||||
int orig_len = header->len;
|
||||
struct ethhdr *eth = (struct ethhdr *)packet;
|
||||
int vlan_id = -1;
|
||||
int eth_offset = sizeof(struct ethhdr);
|
||||
|
||||
if (ntohs(eth->h_proto) != ETH_P_8021Q) {
|
||||
syslog(LOG_DEBUG,
|
||||
"No VLAN header found in packet (EtherType: 0x%04x)\n",
|
||||
ntohs(eth->h_proto));
|
||||
return;
|
||||
}
|
||||
|
||||
struct vlan_hdr *vlan = (struct vlan_hdr *)(packet + eth_offset);
|
||||
vlan_id = ntohs(vlan->h_vlan_TCI) & 0x0FFF;
|
||||
eth_offset += sizeof(struct vlan_hdr);
|
||||
|
||||
struct iface_info *info = find_iface_info_by_vlan(vlan_id);
|
||||
if (!info) {
|
||||
syslog(LOG_ERR, "No interface info found for VLAN ID %d\n", vlan_id);
|
||||
return;
|
||||
}
|
||||
|
||||
char *hostname = get_hostname();
|
||||
int circuit_id_len = strlen(info->bssid) + 1 + strlen(info->essid);
|
||||
int remote_id_len = strlen(hostname);
|
||||
int opt82_len = 2 + 2 + circuit_id_len + 2 + remote_id_len;
|
||||
|
||||
// Find DHCP options end from the end of the packet
|
||||
int ip_offset = eth_offset;
|
||||
struct iphdr *ip = (struct iphdr *)(packet + ip_offset);
|
||||
int udp_offset = ip_offset + (ip->ihl * 4);
|
||||
struct udphdr *udp = (struct udphdr *)(packet + udp_offset);
|
||||
int dhcp_offset = udp_offset + sizeof(struct udphdr);
|
||||
unsigned char *dhcp_start = (unsigned char *)(packet + dhcp_offset);
|
||||
int dhcp_len = ntohs(udp->len) - sizeof(struct udphdr);
|
||||
|
||||
int options_end = -1;
|
||||
for (int i = dhcp_len - 1; i >= 0; i--) {
|
||||
if (dhcp_start[i] == 0xFF) { // End option
|
||||
options_end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (options_end == -1) {
|
||||
syslog(LOG_DEBUG, "Could not find DHCP options end tag\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate new packet size: remove VLAN (-4), remove end tag (-1), add
|
||||
// Option 82, add end tag (+1)
|
||||
int orig_options_len = options_end;
|
||||
int new_len = orig_len - 4 - 1 + opt82_len + 1;
|
||||
unsigned char *new_packet = malloc(new_len);
|
||||
if (!new_packet) {
|
||||
syslog(LOG_ERR, "Failed to allocate memory for new packet\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy Ethernet header
|
||||
struct ethhdr *new_eth = (struct ethhdr *)new_packet;
|
||||
memcpy(new_eth, eth, sizeof(struct ethhdr));
|
||||
new_eth->h_proto = vlan->h_vlan_encapsulated_proto;
|
||||
|
||||
// Copy IP header
|
||||
struct iphdr *new_ip = (struct iphdr *)(new_packet + sizeof(struct ethhdr));
|
||||
memcpy(new_ip, ip, ip->ihl * 4);
|
||||
|
||||
// Copy UDP header
|
||||
struct udphdr *new_udp =
|
||||
(struct udphdr *)(new_packet + sizeof(struct ethhdr) + (ip->ihl * 4));
|
||||
memcpy(new_udp, udp, sizeof(struct udphdr));
|
||||
|
||||
// Copy DHCP payload up to options end, add Option 82, add end tag
|
||||
unsigned char *new_dhcp =
|
||||
(unsigned char *)(new_packet + sizeof(struct ethhdr) + (ip->ihl * 4) +
|
||||
sizeof(struct udphdr));
|
||||
memcpy(new_dhcp, dhcp_start, orig_options_len);
|
||||
|
||||
// Add Option 82
|
||||
int opt82_offset = orig_options_len;
|
||||
new_dhcp[opt82_offset++] = 82; // Option code
|
||||
new_dhcp[opt82_offset++] = opt82_len - 2; // Option length
|
||||
|
||||
// Sub-option 1: Circuit ID (BSSID:ESSID)
|
||||
new_dhcp[opt82_offset++] = 1; // Sub-option code
|
||||
new_dhcp[opt82_offset++] = circuit_id_len; // Sub-option length
|
||||
memcpy(new_dhcp + opt82_offset, info->bssid, strlen(info->bssid));
|
||||
opt82_offset += strlen(info->bssid);
|
||||
new_dhcp[opt82_offset++] = ':';
|
||||
memcpy(new_dhcp + opt82_offset, info->essid, strlen(info->essid));
|
||||
opt82_offset += strlen(info->essid);
|
||||
|
||||
// Sub-option 2: Remote ID (hostname)
|
||||
new_dhcp[opt82_offset++] = 2; // Sub-option code
|
||||
new_dhcp[opt82_offset++] = remote_id_len;
|
||||
memcpy(new_dhcp + opt82_offset, hostname, remote_id_len);
|
||||
opt82_offset += remote_id_len;
|
||||
|
||||
// Add end tag
|
||||
new_dhcp[opt82_offset++] = 0xFF;
|
||||
|
||||
// Update lengths
|
||||
new_ip->tot_len = htons(ntohs(ip->tot_len) + opt82_len);
|
||||
new_udp->len = htons(ntohs(udp->len) + opt82_len);
|
||||
|
||||
// Recalculate IP checksum
|
||||
new_ip->check = 0;
|
||||
unsigned int sum = 0;
|
||||
unsigned short *ip_ptr = (unsigned short *)new_ip;
|
||||
for (int i = 0; i < ip->ihl * 2; i++) {
|
||||
sum += *ip_ptr++;
|
||||
}
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
new_ip->check = ~sum;
|
||||
|
||||
// Recalculate UDP checksum
|
||||
new_udp->check = 0;
|
||||
sum = 0;
|
||||
// Pseudo-header
|
||||
sum +=
|
||||
(ntohs(new_ip->saddr) & 0xFFFF) + (ntohs(new_ip->saddr >> 16) & 0xFFFF);
|
||||
sum +=
|
||||
(ntohs(new_ip->daddr) & 0xFFFF) + (ntohs(new_ip->daddr >> 16) & 0xFFFF);
|
||||
sum += htons(IPPROTO_UDP);
|
||||
sum += new_udp->len;
|
||||
|
||||
// UDP header and data
|
||||
unsigned char *udp_start = (unsigned char *)new_udp;
|
||||
int udp_total_len = ntohs(new_udp->len);
|
||||
unsigned short *udp_ptr = (unsigned short *)udp_start;
|
||||
for (int i = 0; i < udp_total_len / 2; i++) {
|
||||
sum += *udp_ptr++;
|
||||
}
|
||||
if (udp_total_len % 2) {
|
||||
sum += *(unsigned char *)udp_ptr;
|
||||
}
|
||||
while (sum >> 16) {
|
||||
sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
}
|
||||
new_udp->check = ~sum;
|
||||
if (new_udp->check == 0)
|
||||
new_udp->check = 0xFFFF;
|
||||
|
||||
// Send the packet
|
||||
struct sockaddr_ll socket_address = {0};
|
||||
socket_address.sll_family = AF_PACKET;
|
||||
socket_address.sll_protocol = htons(ETH_P_ALL);
|
||||
|
||||
for (int i = 0; i < port_count; i++) {
|
||||
socket_address.sll_ifindex = ports[i].ifindex;
|
||||
|
||||
if (sendto(ports[i].sock, new_packet, new_len, 0,
|
||||
(struct sockaddr *)&socket_address,
|
||||
sizeof(socket_address)) < 0) {
|
||||
syslog(LOG_ERR, "Failed to send packet to %s: %s\n",
|
||||
ports[i].name, strerror(errno));
|
||||
} else {
|
||||
syslog(LOG_DEBUG,
|
||||
"Successfully forwarded packet to %s (new length: %d)\n",
|
||||
ports[i].name, new_len);
|
||||
}
|
||||
}
|
||||
|
||||
free(new_packet);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
openlog("dhcp_inject:", LOG_PID | LOG_CONS, LOG_DAEMON);
|
||||
|
||||
signal(SIGTERM, signal_handler);
|
||||
signal(SIGHUP, signal_handler);
|
||||
|
||||
provided_ssids = getenv("SSIDs");
|
||||
syslog(LOG_INFO, "Provided SSIDs: %s\n", provided_ssids);
|
||||
if (!provided_ssids && argc > 1) {
|
||||
provided_ssids = strdup(argv[1]);
|
||||
}
|
||||
if (!provided_ssids) {
|
||||
syslog(LOG_ERR, "No SSIDs provided. Exiting...\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
provided_ports = getenv("PORTs");
|
||||
syslog(LOG_INFO, "Provided PORTs: %s\n", provided_ports);
|
||||
if (!provided_ports) {
|
||||
syslog(LOG_ERR, "No PORTs provided. Exiting...\n");
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_ssids(provided_ssids) != 0) {
|
||||
syslog(LOG_ERR, "Failed to parse SSIDs\n");
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parse_ports(provided_ports) != 0) {
|
||||
syslog(LOG_ERR, "Failed to parse ports\n");
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (setup_tc() != 0) {
|
||||
syslog(LOG_ERR, "Setup failed\n");
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
syslog(LOG_INFO, "Setup complete for SSIDs: %s and Ports: %s\n",
|
||||
provided_ssids, provided_ports);
|
||||
|
||||
char errbuf[PCAP_ERRBUF_SIZE];
|
||||
handle = pcap_open_live("ifb-inject", BUFSIZ, 1, 1000, errbuf);
|
||||
if (handle == NULL) {
|
||||
syslog(LOG_ERR, "Couldn't open device ifb-inject: %s\n", errbuf);
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pcap_loop(handle, -1, process_packet, NULL) < 0) {
|
||||
syslog(LOG_ERR, "pcap_loop failed: %s\n", pcap_geterr(handle));
|
||||
cleanup();
|
||||
return 1;
|
||||
}
|
||||
|
||||
cleanup();
|
||||
return 0;
|
||||
}
|
||||
46
feeds/ucentral/udhcpinject/src/udhcpinject.h
Normal file
46
feeds/ucentral/udhcpinject/src/udhcpinject.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <stdint.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#define LEN_ESSID 32
|
||||
#define LEN_BSSID 12
|
||||
#define LEN_IFACE 15
|
||||
|
||||
// DHCP header structure
|
||||
struct dhcp_packet {
|
||||
uint8_t op;
|
||||
uint8_t htype;
|
||||
uint8_t hlen;
|
||||
uint8_t hops;
|
||||
uint32_t xid;
|
||||
uint16_t secs;
|
||||
uint16_t flags;
|
||||
struct in_addr ciaddr;
|
||||
struct in_addr yiaddr;
|
||||
struct in_addr siaddr;
|
||||
struct in_addr giaddr;
|
||||
uint8_t chaddr[16];
|
||||
char sname[64];
|
||||
char file[128];
|
||||
uint32_t magic;
|
||||
uint8_t options[];
|
||||
};
|
||||
|
||||
// Structure to hold interface info
|
||||
struct iface_info {
|
||||
char iface[LEN_IFACE + 1];
|
||||
char essid[LEN_ESSID + 1];
|
||||
char bssid[LEN_BSSID + 1];
|
||||
int serial;
|
||||
};
|
||||
|
||||
struct port_info {
|
||||
char name[LEN_IFACE + 1];
|
||||
int sock;
|
||||
int ifindex;
|
||||
};
|
||||
|
||||
// VLAN header structure
|
||||
struct vlan_hdr {
|
||||
__be16 h_vlan_TCI; // VLAN Tag Control Information
|
||||
__be16 h_vlan_encapsulated_proto; // Encapsulated protocol
|
||||
};
|
||||
Reference in New Issue
Block a user