Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,41 @@
#
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
# template version 3
#
# module setup:
# - ${MODULE_NAME}: module name
ev_setup_cpp_module()
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# insert your custom targets and additional config variables here
target_link_libraries(${MODULE_NAME}
PRIVATE
everest::run_application
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"iso15118_vas/ISO15118_vasImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# Check for radvd and udhcpd executables
find_program(RADVD_EXECUTABLE radvd)
if(NOT RADVD_EXECUTABLE)
message(WARNING "radvd executable not found. The Iso15118InternetVas module will not work correctly.")
endif()
find_program(UDHCPD_EXECUTABLE udhcpd)
if(NOT UDHCPD_EXECUTABLE)
message(WARNING "udhcpd executable not found. The Iso15118InternetVas module will not work correctly.")
endif()
install(
PROGRAMS
vas-internet-setup.sh
DESTINATION "${EVEREST_MODULE_INSTALL_PREFIX}/${MODULE_NAME}"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "Iso15118InternetVas.hpp"
namespace module {
void Iso15118InternetVas::init() {
invoke_init(*p_iso15118_vas);
}
void Iso15118InternetVas::ready() {
invoke_ready(*p_iso15118_vas);
}
} // namespace module

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO15118INTERNET_VAS_HPP
#define ISO15118INTERNET_VAS_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/ISO15118_vas/Implementation.hpp>
// headers for required interface implementations
#include <generated/interfaces/evse_manager/Interface.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
// insert your custom include headers here
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string ev_interface;
std::string modem_interface;
bool http_support;
bool https_support;
std::string vas_setup_script;
};
class Iso15118InternetVas : public Everest::ModuleBase {
public:
Iso15118InternetVas() = delete;
Iso15118InternetVas(const ModuleInfo& info, std::unique_ptr<ISO15118_vasImplBase> p_iso15118_vas,
std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager, Conf& config) :
ModuleBase(info),
p_iso15118_vas(std::move(p_iso15118_vas)),
r_evse_manager(std::move(r_evse_manager)),
config(config){};
const std::unique_ptr<ISO15118_vasImplBase> p_iso15118_vas;
const std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
// insert your public definitions here
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // ISO15118INTERNET_VAS_HPP

View File

@@ -0,0 +1,123 @@
.. _everest_modules_handwritten_Iso15118InternetVas:
.. *******************************************
.. Iso15118InternetVas
.. *******************************************
.. warning::
This module and its helper scripts are **examples** and not intended for
production use without modification. The provided ``vas-internet-setup.sh``
script is a starting point and may not cover all edge cases for your
specific hardware and network environment. It is the user's responsibility
to ensure the configuration is secure and robust.
This module implements the ISO 15118-2 Value Added Service (VAS) for providing
internet access to a connected electric vehicle (EV). When an EV requests this
service, the module configures the charger's networking to share its internet
connection with the EV over the power line communication (PLC) interface.
How it works
============
1. **Service Announcement**: The module advertises the availability of the
Internet Access service (Service ID 3) to the EV, offering HTTP and/or HTTPS
access based on the module's configuration.
2. **Service Selection**: If the EV selects this service, the module initiates
the network setup.
3. **Network Setup**: The module executes a helper script,
``vas-internet-setup.sh``, to configure all necessary networking components.
The script's behavior depends on the configuration and the services selected
by the EV:
- It enables IPv6 forwarding and sets up NAT using ``ip6tables``.
- If IPv4 support is enabled in the configuration, it also:
- Starts a DHCPv4 server (``udhcpd``) on the EV-facing network
interface (``ev_interface``). This DHCP server provides the EV with an
IPv4 address and DNS server information.
- Enables IPv4 forwarding and NAT using ``iptables``.
- It starts a Router Advertisement Daemon (``radvd``) on the
``ev_interface`` to enable the EV to configure an IPv6 address using SLAAC.
This also includes advertising Recursive DNS Servers (RDNSS) for IPv6.
- The module determines which ports to open based on the parameter sets
(HTTP, HTTPS) selected by the EV. The forwarding is then restricted to
allow only TCP traffic on the selected ports (80 for HTTP, 443 for HTTPS).
- The example script uses public DNS servers (Google's 8.8.8.8 for IPv4
and 2001:4860:4860::8888 for IPv6). This can be changed in the script.
4. **Session Teardown**: When the charging session ends (signaled by the
``evse_manager``), the module calls the same script to automatically tear
down the network configuration, stopping the services and removing all
forwarding and NAT rules.
Requirements
============
Software
--------
The module relies on the ``vas-internet-setup.sh`` script, which requires the
following external tools to be available in the system's PATH:
- ``radvd``: For IPv6 Router Advertisements.
- ``ip6tables``: For setting up IPv6 NAT and forwarding rules.
- ``ip`` (from the ``iproute2`` package): For network interface configuration.
- ``iptables`` (optional, for IPv4): For IPv4 NAT and forwarding rules.
- ``udhcpd`` (optional, for IPv4): For the DHCPv4 server (typically provided by
BusyBox).
The EVerest framework must be run with sufficient privileges to allow these
tools to modify network settings. This typically means running as the ``root``
user.
Hardware
--------
- A working internet connection on the charging station.
- A network interface that is connected to the internet (e.g., ``eth0``, ``wwan0``).
- A network interface for the Power Line Communication (PLC) modem that
communicates with the EV (e.g., a HomePlug Green PHY modem connected via
Ethernet).
Configuration
=============
.. list-table::
:widths: 25 75
:header-rows: 1
* - Key
- Description
* - ``ev_interface``
- The name of the network interface connected to the EV via the PLC modem.
* - ``modem_interface``
- The name of the network interface connected to the internet.
* - ``http_support``
- (boolean) Whether to announce support for HTTP (Port 80). Defaults to `true`.
* - ``https_support``
- (boolean) Whether to announce support for HTTPS (Port 443). Defaults to `true`.
Example Configuration
---------------------
.. code-block:: yaml
- module: Iso15118InternetVas
config:
ev_interface: eth1
modem_interface: eth0
http_support: true
https_support: true
Provided Interfaces
===================
- **iso15118_vas**: Implements the ``ISO15118_vas`` interface to handle service
discovery and selection from the EV.
Required Interfaces
===================
- **evse_manager**: The module optionally connects to an ``evse_manager`` to
monitor the charging session. When the session finishes, it triggers the
teardown of the internet connection for the EV.

View File

@@ -0,0 +1,187 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <algorithm>
#include <everest/run_application/run_application.hpp>
#include <generated/types/iso15118_vas.hpp>
#include "ISO15118_vasImpl.hpp"
#include <sys/types.h>
namespace fs = std::filesystem;
namespace module {
namespace iso15118_vas {
constexpr int32_t InternetAccessServiceIdD2 = 3;
constexpr int HTTP_PARAM_SET_ID = 3;
constexpr int HTTPS_PARAM_SET_ID = 4;
constexpr int HTTP_PORT = 80;
constexpr int HTTPS_PORT = 443;
const std::string PARAM_NAME_PROTOCOL = "Protocol";
const std::string PARAM_NAME_PORT = "Port";
const std::string VALUE_HTTP = "http";
const std::string VALUE_HTTPS = "https";
ISO15118_vasImpl::~ISO15118_vasImpl() {
this->stop_internet_service();
}
void ISO15118_vasImpl::init() {
if (!this->mod->r_evse_manager.empty()) {
this->mod->r_evse_manager.at(0)->subscribe_session_event(
[this](types::evse_manager::SessionEvent session_event) {
if (session_event.event == types::evse_manager::SessionEventEnum::SessionFinished and
this->internet_service_running) {
this->stop_internet_service();
}
});
}
const auto config_setup_script = fs::path(this->mod->config.vas_setup_script);
if (config_setup_script.is_relative()) {
this->internet_setup_script = (this->mod->info.paths.libexec / this->mod->config.vas_setup_script).string();
} else {
this->internet_setup_script = this->mod->config.vas_setup_script;
}
}
void ISO15118_vasImpl::ready() {
this->publish_offered_vas({{{InternetAccessServiceIdD2}}});
}
std::vector<types::iso15118_vas::ParameterSet> ISO15118_vasImpl::handle_get_service_parameters(int& service_id) {
std::vector<types::iso15118_vas::ParameterSet> ret{};
if (service_id == InternetAccessServiceIdD2) {
ret.reserve(2);
if (this->mod->config.http_support) {
// HTTP
types::iso15118_vas::ParameterSet http_params;
http_params.set_id = HTTP_PARAM_SET_ID;
http_params.parameters.reserve(2);
types::iso15118_vas::Parameter http_param;
http_param.name = PARAM_NAME_PROTOCOL;
types::iso15118_vas::ParameterValue http_protocol_name;
http_protocol_name.finite_string = VALUE_HTTP;
http_param.value = http_protocol_name;
types::iso15118_vas::Parameter http_port;
http_port.name = PARAM_NAME_PORT;
types::iso15118_vas::ParameterValue http_port_value;
http_port_value.int_value = HTTP_PORT;
http_port.value = http_port_value;
http_params.parameters.emplace_back(http_param);
http_params.parameters.emplace_back(http_port);
ret.emplace_back(http_params);
}
if (this->mod->config.https_support) {
// HTTPS
types::iso15118_vas::ParameterSet https_params;
https_params.set_id = HTTPS_PARAM_SET_ID;
https_params.parameters.reserve(2);
types::iso15118_vas::Parameter https_param;
https_param.name = PARAM_NAME_PROTOCOL;
types::iso15118_vas::ParameterValue https_protocol_name;
https_protocol_name.finite_string = VALUE_HTTPS;
https_param.value = https_protocol_name;
types::iso15118_vas::Parameter https_port;
https_port.name = PARAM_NAME_PORT;
types::iso15118_vas::ParameterValue https_port_value;
https_port_value.int_value = HTTPS_PORT;
https_port.value = https_port_value;
https_params.parameters.emplace_back(https_param);
https_params.parameters.emplace_back(https_port);
ret.emplace_back(https_params);
}
}
return ret;
}
std::vector<int> ISO15118_vasImpl::get_selected_internet_ports(
const std::vector<types::iso15118_vas::SelectedService>& selected_services) {
std::vector<int> selected_ports;
for (const auto& service : selected_services) {
if (service.service_id == InternetAccessServiceIdD2) {
if (this->mod->config.http_support and service.parameter_set_id == HTTP_PARAM_SET_ID) {
if (std::find(selected_ports.begin(), selected_ports.end(), HTTP_PORT) == selected_ports.end()) {
selected_ports.push_back(HTTP_PORT);
}
} else if (this->mod->config.https_support and service.parameter_set_id == HTTPS_PARAM_SET_ID) {
if (std::find(selected_ports.begin(), selected_ports.end(), HTTPS_PORT) == selected_ports.end()) {
selected_ports.push_back(HTTPS_PORT);
}
}
}
}
return selected_ports;
}
void ISO15118_vasImpl::handle_selected_services(std::vector<types::iso15118_vas::SelectedService>& selected_services) {
const auto ports_to_open = this->get_selected_internet_ports(selected_services);
if (!ports_to_open.empty()) {
std::string ports_str;
for (size_t i = 0; i < ports_to_open.size(); ++i) {
ports_str += std::to_string(ports_to_open[i]);
if (i < ports_to_open.size() - 1) {
ports_str += ",";
}
}
start_internet_service(ports_str);
}
}
void ISO15118_vasImpl::start_script(const std::string& script_name, const std::vector<std::string>& args) {
auto output = everest::run_application::run_application(script_name, args);
if (output.exit_code != 0) {
EVLOG_warning << "Script: " << script_name << " exited with code: " << output.exit_code;
EVLOG_warning << "Script output:";
EVLOG_warning << output.output;
}
}
void ISO15118_vasImpl::start_internet_service(const std::string& ports) {
{
std::lock_guard lock(internet_mutex);
if (this->internet_service_running) {
EVLOG_warning << "Internet service is already running.";
return;
}
this->internet_service_running = true;
}
this->active_ports = ports;
EVLOG_info << "Starting internet service for ports: " << this->active_ports;
std::thread(&ISO15118_vasImpl::start_script, this, this->internet_setup_script,
std::vector<std::string>{"up", this->mod->config.ev_interface, this->mod->config.modem_interface,
this->active_ports})
.detach();
}
void ISO15118_vasImpl::stop_internet_service() {
{
std::lock_guard lock(internet_mutex);
if (!this->internet_service_running) {
EVLOG_warning << "Internet service is not running.";
return;
}
this->internet_service_running = false;
}
EVLOG_info << "Stopping internet service for ports: " << this->active_ports;
std::thread(&ISO15118_vasImpl::start_script, this, this->internet_setup_script,
std::vector<std::string>{"down", this->mod->config.ev_interface, this->mod->config.modem_interface,
this->active_ports})
.detach();
this->active_ports.clear();
}
} // namespace iso15118_vas
} // namespace module

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ISO15118_VAS_ISO15118_VAS_IMPL_HPP
#define ISO15118_VAS_ISO15118_VAS_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/ISO15118_vas/Implementation.hpp>
#include "../Iso15118InternetVas.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
#include <mutex>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace iso15118_vas {
struct Conf {};
class ISO15118_vasImpl : public ISO15118_vasImplBase {
public:
ISO15118_vasImpl() = delete;
ISO15118_vasImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Iso15118InternetVas>& mod, Conf& config) :
ISO15118_vasImplBase(ev, "iso15118_vas"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
~ISO15118_vasImpl() override;
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual std::vector<types::iso15118_vas::ParameterSet> handle_get_service_parameters(int& service_id) override;
virtual void
handle_selected_services(std::vector<types::iso15118_vas::SelectedService>& selected_services) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<Iso15118InternetVas>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
void start_script(const std::string& script_name, const std::vector<std::string>& args);
void start_internet_service(const std::string& ports);
void stop_internet_service();
std::vector<int>
get_selected_internet_ports(const std::vector<types::iso15118_vas::SelectedService>& selected_services);
std::string internet_setup_script = "";
bool internet_service_running{false};
std::mutex internet_mutex;
std::string active_ports;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace iso15118_vas
} // namespace module
#endif // ISO15118_VAS_ISO15118_VAS_IMPL_HPP

View File

@@ -0,0 +1,41 @@
description: >-
This module provides ISO15118 internet value-added services (VAS).
config:
ev_interface:
description: >-
Name of the interface which is connected to the EV.
type: string
modem_interface:
description: >-
Name of the interface which is connected to the internet.
type: string
http_support:
description: >-
Boolean dictating whether we should let the EV know we support HTTP
type: boolean
https_support:
description: >-
Boolean dictating whether we should let the EV know we support HTTPS
type: boolean
vas_setup_script:
description: >-
Filepath of the script, which is executed to setup the VaS network.
A relative filepath is appended to the module directory:
/usr/libexec/everest/modules/Iso15118InternetVas/.
That means in the default case, it expands to: /usr/libexec/everest/modules/Iso15118InternetVas/vas-internet-setup.sh.
An absolute filepath is directly executed.
type: string
default: "vas-internet-setup.sh"
provides:
iso15118_vas:
interface: ISO15118_vas
description: The internet VAS
requires:
evse_manager:
interface: evse_manager
min_connections: 0
max_connections: 1
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Martin Litre, Pionix GmbH

View File

@@ -0,0 +1,283 @@
#!/bin/sh
# --------------------------------------------------------------------------
#
# WARNING: This script is an example and not intended for production use.
#
# This script is provided as a starting point for setting up internet
# access for an EV. It is not a complete solution and may require
# modification to work in your specific environment.
#
# It is the responsibility of the user to ensure that the script is
# secure and does not introduce any security vulnerabilities.
#
# --------------------------------------------------------------------------
# This script manages the full network setup to provide internet access
# to an EV. It handles IP forwarding, NAT, a DHCP server, and a
# router advertisement daemon.
set -euo pipefail
# --- Configuration ---
DHCP_STATIC_IP="172.18.200.1"
SUBNET_MASK="24"
RADVD_IPV6_PREFIX="fd00::/64"
# --- File Paths ---
PID_DIR="/var/run"
DHCP_PID_FILE="${PID_DIR}/vas_udhcpd.pid"
RADVD_PID_FILE="${PID_DIR}/vas_radvd.pid"
CONF_DIR="/tmp"
DHCP_CONF_FILE="${CONF_DIR}/vas_udhcpd.conf"
RADVD_CONF_FILE="${CONF_DIR}/vas_radvd.conf"
# Load optional configuration from /etc/default/vas-internet
[ -f /etc/default/vas-internet ] && . /etc/default/vas-internet
# --- Helper Functions ---
need_root() {
if [[ $EUID -ne 0 ]]; then
echo "Please run as root (use sudo)." >&2
exit 1
fi
}
cmd_exists() {
command -v "$1" >/dev/null 2>&1
}
check_deps() {
local use_ipv4=$1
local deps="ip ip6tables radvd"
if [ "$use_ipv4" = true ]; then
deps="$deps iptables udhcpd"
fi
for cmd in $deps; do
if ! cmd_exists "$cmd"; then
echo "Error: Command '$cmd' not found. Please install it." >&2
exit 1
fi
done
}
# --- Core Logic Functions ---
start_services() {
local LAN_IFACE=$1
local WAN_IFACE=$2
local PORTS=${3:-}
local USE_IPV4=$4
echo "[+] Starting all internet VAS services for $LAN_IFACE -> $WAN_IFACE"
# 1. Enable IP Forwarding
echo " [1/4] Enabling IP forwarding"
if [ "$USE_IPV4" = true ]; then
sysctl -w net.ipv4.ip_forward=1 >/dev/null
fi
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null
# 2. Setup NAT and Forwarding Rules
echo " [2/4] Setting up NAT and FORWARD rules on $WAN_IFACE"
# Create custom chains
if [ "$USE_IPV4" = true ]; then
iptables -N VAS_FORWARD 2>/dev/null || iptables -F VAS_FORWARD
fi
ip6tables -N VAS_FORWARD 2>/dev/null || ip6tables -F VAS_FORWARD
# Jump to custom chain
if [ "$USE_IPV4" = true ]; then
iptables -C FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD 2>/dev/null || \
iptables -A FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD
fi
ip6tables -C FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD 2>/dev/null || \
ip6tables -A FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD
# MASQUERADE rule for outbound traffic on WAN interface
if [ "$USE_IPV4" = true ]; then
iptables -t nat -C POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || \
iptables -t nat -A POSTROUTING -o "$WAN_IFACE" -j MASQUERADE
fi
ip6tables -t nat -C POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || \
ip6tables -t nat -A POSTROUTING -o "$WAN_IFACE" -j MASQUERADE
# Allow return traffic for established connections
if [ "$USE_IPV4" = true ]; then
iptables -C FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -A FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
fi
ip6tables -C FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
ip6tables -A FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT
# Add specific forwarding rules for outbound traffic from LAN interface
echo " - Allowing outgoing traffic on TCP ports: $PORTS"
if [ "$USE_IPV4" = true ]; then
# TCP
iptables -A VAS_FORWARD -p tcp -m multiport --dports "$PORTS" -j ACCEPT
fi
# TCP (IPv6)
ip6tables -A VAS_FORWARD -p tcp -m multiport --dports "$PORTS" -j ACCEPT
# 3. Start DHCP server
if [ "$USE_IPV4" = true ]; then
echo " [3/4] Starting DHCPv4 server on $LAN_IFACE"
local IP_WITH_SUBNET="${DHCP_STATIC_IP}/${SUBNET_MASK}"
ip addr show dev "${LAN_IFACE}" | grep -q "${IP_WITH_SUBNET}" || ip addr add "${IP_WITH_SUBNET}" dev "${LAN_IFACE}"
local NETWORK_BASE=$(echo "$DHCP_STATIC_IP" | cut -d. -f1-3)
cat > "${DHCP_CONF_FILE}" << EOF
interface ${LAN_IFACE}
start ${NETWORK_BASE}.2
end ${NETWORK_BASE}.200
option subnet 255.255.255.0
option router ${DHCP_STATIC_IP}
option dns 8.8.8.8 8.8.4.4
option lease 864000
pidfile ${DHCP_PID_FILE}
EOF
udhcpd -S "${DHCP_CONF_FILE}"
else
echo " [3/4] Skipping DHCPv4 server (IPv4 disabled)"
fi
# 4. Start RADVD server
echo " [4/4] Starting DHCPv6/RADVD server on $LAN_IFACE"
cat > "${RADVD_CONF_FILE}" << EOF
interface ${LAN_IFACE}
{
AdvSendAdvert on;
MinRtrAdvInterval 30;
MaxRtrAdvInterval 100;
prefix ${RADVD_IPV6_PREFIX}
{
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr off;
};
RDNSS 2001:4860:4860::8888 2001:4860:4860::8844
{
AdvRDNSSLifetime 3600;
};
};
EOF
radvd -C "${RADVD_CONF_FILE}" -p "${RADVD_PID_FILE}"
echo "[✓] All services started."
}
stop_services() {
local LAN_IFACE=$1
local WAN_IFACE=$2
local PORTS=$3
local USE_IPV4=$4
echo "[+] Stopping all internet VAS services for $LAN_IFACE -> $WAN_IFACE"
# 1. Stop daemons
if [ "$USE_IPV4" = true ]; then
echo " [1/3] Stopping DHCPv4 server"
if [ -f "$DHCP_PID_FILE" ]; then
kill "$(cat "$DHCP_PID_FILE")" || echo "DHCP server already stopped."
rm -f "$DHCP_PID_FILE"
else
echo "DHCP PID file not found. Maybe it was not running."
fi
fi
echo " [2/3] Stopping DHCPv6/RADVD server"
if [ -f "$RADVD_PID_FILE" ]; then
kill "$(cat "$RADVD_PID_FILE")" || echo "RADVD server already stopped."
rm -f "$RADVD_PID_FILE"
else
echo "RADVD PID file not found. Maybe it was not running."
fi
# 2. Remove NAT and FORWARD rules
echo " [3/3] Removing NAT and FORWARD rules"
# Remove jump rule
if [ "$USE_IPV4" = true ]; then
iptables -D FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD 2>/dev/null || true
fi
ip6tables -D FORWARD -i "$LAN_IFACE" -o "$WAN_IFACE" -j VAS_FORWARD 2>/dev/null || true
# Flush and delete custom chain
if [ "$USE_IPV4" = true ]; then
iptables -F VAS_FORWARD 2>/dev/null || true
iptables -X VAS_FORWARD 2>/dev/null || true
fi
ip6tables -F VAS_FORWARD 2>/dev/null || true
ip6tables -X VAS_FORWARD 2>/dev/null || true
if [ "$USE_IPV4" = true ]; then
iptables -t nat -D POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || true
iptables -D FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
fi
ip6tables -t nat -D POSTROUTING -o "$WAN_IFACE" -j MASQUERADE 2>/dev/null || true
ip6tables -D FORWARD -i "$WAN_IFACE" -o "$LAN_IFACE" -m state --state RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || true
echo "[✓] All services stopped and rules removed."
}
usage() {
cat <<EOF
Usage:
sudo $0 up <LAN_IFACE> <WAN_IFACE> <PORTS> [--ipv4]
sudo $0 down <LAN_IFACE> <WAN_IFACE> <PORTS> [--ipv4]
Commands:
up - Configures interfaces, enables NAT/forwarding, and starts DHCP/RADVD.
down - Stops daemons and removes all created network rules.
Arguments:
PORTS - Comma-separated list of TCP ports to allow (e.g., "80,443").
--ipv4 - Optional flag to enable all IPv4-related setup (disabled by default).
EOF
}
main() {
need_root
local use_ipv4=false
local args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--ipv4)
use_ipv4=true
shift
;;
*)
args+=("$1")
shift
;;
esac
done
check_deps "$use_ipv4"
local cmd="${args[0]:-}"
if [[ ${#args[@]} -ne 4 ]]; then
usage
exit 1
fi
case "$cmd" in
up)
start_services "${args[1]}" "${args[2]}" "${args[3]}" "$use_ipv4"
;;
down)
stop_services "${args[1]}" "${args[2]}" "${args[3]}" "$use_ipv4"
;;
*)
usage
exit 1
;;
esac
}
main "$@"