Browse Source

feat: переименован allowed_subnet в route_subnet, добавлено управление системными маршрутами

- Переименовано поле allowed_subnets -> route_subnets в конфигурации
- Обновлен парсинг конфига: allowed_subnet -> route_subnet
- Создан новый модуль tun_route для управления системными маршрутами
- Linux: реализация через netlink sockets с fallback на ip route
- Windows: реализация через IP Helper API
- BSD: реализация через routing sockets с fallback на route
- При запуске: автоматическое добавление маршрутов route_subnet на TUN интерфейс
- При завершении: автоматическое удаление всех маршрутов через tun_route_flush
- Обновлены Makefile.am для сборки новых файлов
nodeinfo-routing-update
Evgeny 2 months ago
parent
commit
4cd22b1513
  1. 2
      .gitignore
  2. 1
      src/Makefile.am
  3. 10
      src/config_parser.c
  4. 2
      src/config_parser.h
  5. 429
      src/tun_route.c
  6. 51
      src/tun_route.h
  7. 6
      src/utun.conf
  8. 12
      src/utun_instance.c
  9. 1
      tests/Makefile.am
  10. 44
      utun.conf
  11. 32
      utun.conf.sample

2
.gitignore vendored

@ -61,6 +61,8 @@ tests/test_*
# Local configs
*.cfg
!utun_test.cfg
utun.conf
!utun.conf.sample
# MSVC temp files

1
src/Makefile.am

@ -10,6 +10,7 @@ utun_CORE_SOURCES = \
route_bgp.c \
routing.c \
tun_if.c \
tun_route.c \
etcp.c \
etcp_connections.c \
etcp_loadbalancer.c \

10
src/config_parser.c

@ -509,8 +509,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
}
break;
case SECTION_ROUTING:
if (strcmp(key, "allowed_subnet") == 0) {
add_route_entry(&cfg->allowed_subnets, value);
if (strcmp(key, "route_subnet") == 0) {
add_route_entry(&cfg->route_subnets, value);
} else if (strcmp(key, "my_subnet") == 0) {
add_route_entry(&cfg->my_subnets, value);
}
@ -584,7 +584,7 @@ void free_config(struct utun_config *config) {
}
// Free route entries
free_route_entries(config->allowed_subnets);
free_route_entries(config->route_subnets);
free_route_entries(config->my_subnets);
free(config);
@ -647,8 +647,8 @@ void print_config(const struct utun_config *cfg) {
c = c->next;
}
printf("\nAllowed Subnets:\n");
struct CFG_ROUTE_ENTRY *allowed = cfg->allowed_subnets;
printf("\nRoute Subnets:\n");
struct CFG_ROUTE_ENTRY *allowed = cfg->route_subnets;
while (allowed) {
printf(" %s/%d\n", ip_to_string(&allowed->ip, ip_buffer, sizeof(ip_buffer)), allowed->netmask);
allowed = allowed->next;

2
src/config_parser.h

@ -83,7 +83,7 @@ struct utun_config {
struct global_config global;
struct CFG_SERVER* servers;
struct CFG_CLIENT* clients;
struct CFG_ROUTE_ENTRY* allowed_subnets;
struct CFG_ROUTE_ENTRY* route_subnets;
struct CFG_ROUTE_ENTRY* my_subnets;
};

429
src/tun_route.c

@ -0,0 +1,429 @@
// tun_route.c - Cross-platform system routing table management
// Linux: netlink sockets, Windows: IP Helper API, BSD: routing sockets
#include "tun_route.h"
#include "config_parser.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// Platform detection
#ifdef _WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#elif defined(__linux__)
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <unistd.h>
#include <net/if.h>
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
#include <sys/socket.h>
#include <net/route.h>
#include <net/if.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
// Convert prefix length to netmask
static uint32_t prefix_to_netmask(uint8_t prefix_len) {
return (prefix_len == 0) ? 0 : htonl(~((1U << (32 - prefix_len)) - 1));
}
#ifdef __linux__
// Linux netlink implementation
struct nl_req {
struct nlmsghdr nl;
struct rtmsg rt;
char buf[256];
};
static int netlink_route(int ifindex, uint32_t network, uint8_t prefix_len, int cmd, int flags) {
int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create netlink socket: %s", strerror(errno));
return -1;
}
struct nl_req req;
memset(&req, 0, sizeof(req));
req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.nl.nlmsg_type = cmd; // RTM_NEWROUTE or RTM_DELROUTE
req.nl.nlmsg_flags = flags | NLM_F_REQUEST;
req.nl.nlmsg_seq = 1;
req.nl.nlmsg_pid = getpid();
req.rt.rtm_family = AF_INET;
req.rt.rtm_table = RT_TABLE_MAIN;
req.rt.rtm_protocol = RTPROT_STATIC;
req.rt.rtm_scope = RT_SCOPE_UNIVERSE;
req.rt.rtm_type = RTN_UNICAST;
req.rt.rtm_dst_len = prefix_len;
// Add destination address attribute
struct rtattr *rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nl.nlmsg_len));
rta->rta_type = RTA_DST;
rta->rta_len = RTA_LENGTH(4);
memcpy(RTA_DATA(rta), &network, 4);
req.nl.nlmsg_len = NLMSG_ALIGN(req.nl.nlmsg_len) + RTA_ALIGN(rta->rta_len);
// Add output interface attribute
rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nl.nlmsg_len));
rta->rta_type = RTA_OIF;
rta->rta_len = RTA_LENGTH(4);
memcpy(RTA_DATA(rta), &ifindex, 4);
req.nl.nlmsg_len = NLMSG_ALIGN(req.nl.nlmsg_len) + RTA_ALIGN(rta->rta_len);
if (send(fd, &req, req.nl.nlmsg_len, 0) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to send netlink message: %s", strerror(errno));
close(fd);
return -1;
}
// Receive response
char reply[4096];
ssize_t len = recv(fd, reply, sizeof(reply), 0);
if (len < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to receive netlink response: %s", strerror(errno));
close(fd);
return -1;
}
struct nlmsghdr *nl_hdr = (struct nlmsghdr *)reply;
if (nl_hdr->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nl_hdr);
if (err->error < 0) {
errno = -err->error;
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Netlink error: %s", strerror(errno));
close(fd);
return -1;
}
}
close(fd);
return 0;
}
// Fallback: use ip route command
static int ip_route_cmd(const char *ifname, uint32_t network, uint8_t prefix_len, const char *cmd) {
char network_str[INET_ADDRSTRLEN];
struct in_addr addr;
addr.s_addr = network;
if (!inet_ntop(AF_INET, &addr, network_str, sizeof(network_str))) {
return -1;
}
char cmdline[256];
snprintf(cmdline, sizeof(cmdline), "ip route %s %s/%d dev %s 2>/dev/null",
cmd, network_str, prefix_len, ifname);
int ret = system(cmdline);
return (ret == 0) ? 0 : -1;
}
int tun_route_add(const char *ifname, uint32_t network, uint8_t prefix_len) {
int ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Interface %s not found", ifname);
return -1;
}
network = htonl(network);
// Try netlink first
if (netlink_route(ifindex, network, prefix_len, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL) == 0) {
return 0;
}
// Fallback to ip route command
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Netlink failed, trying ip route command for %s", ifname);
return ip_route_cmd(ifname, ntohl(network), prefix_len, "add");
}
int tun_route_del(const char *ifname, uint32_t network, uint8_t prefix_len) {
int ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Interface %s not found", ifname);
return -1;
}
network = htonl(network);
// Try netlink first
if (netlink_route(ifindex, network, prefix_len, RTM_DELROUTE, 0) == 0) {
return 0;
}
// Fallback to ip route command
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Netlink failed, trying ip route command for %s", ifname);
return ip_route_cmd(ifname, ntohl(network), prefix_len, "del");
}
int tun_route_flush(const char *ifname) {
// Use ip route flush as primary method - works reliably
char cmdline[256];
snprintf(cmdline, sizeof(cmdline), "ip route flush dev %s 2>/dev/null", ifname);
int ret = system(cmdline);
if (ret != 0) {
// Fallback: try to list and delete routes one by one
DEBUG_WARN(DEBUG_CATEGORY_TUN, "ip route flush failed, trying alternative method");
snprintf(cmdline, sizeof(cmdline),
"ip route show dev %s 2>/dev/null | while read r; do ip route del $r dev %s 2>/dev/null; done",
ifname, ifname);
ret = system(cmdline);
}
return (ret == 0) ? 0 : -1;
}
#elif defined(_WIN32)
// Windows IP Helper API implementation
int tun_route_add(const char *ifname, uint32_t network, uint8_t prefix_len) {
MIB_IPFORWARDROW route;
memset(&route, 0, sizeof(route));
route.dwForwardDest = htonl(network);
route.dwForwardMask = prefix_to_netmask(prefix_len);
route.dwForwardPolicy = 0;
route.dwForwardNextHop = 0; // On-link route
route.dwForwardIfIndex = if_nametoindex(ifname);
route.dwForwardType = MIB_IPROUTE_TYPE_DIRECT;
route.dwForwardProto = MIB_IPPROTO_NETMGMT;
route.dwForwardAge = 0;
route.dwForwardNextHopAS = 0;
route.dwForwardMetric1 = 1;
route.dwForwardMetric2 = 0;
route.dwForwardMetric3 = 0;
route.dwForwardMetric4 = 0;
route.dwForwardMetric5 = 0;
DWORD ret = CreateIpForwardEntry(&route);
if (ret != NO_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add route: %lu", ret);
return -1;
}
return 0;
}
int tun_route_del(const char *ifname, uint32_t network, uint8_t prefix_len) {
MIB_IPFORWARDROW route;
memset(&route, 0, sizeof(route));
route.dwForwardDest = htonl(network);
route.dwForwardMask = prefix_to_netmask(prefix_len);
route.dwForwardIfIndex = if_nametoindex(ifname);
DWORD ret = DeleteIpForwardEntry(&route);
if (ret != NO_ERROR) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to delete route: %lu", ret);
return -1;
}
return 0;
}
int tun_route_flush(const char *ifname) {
PMIB_IPFORWARDTABLE table = NULL;
DWORD size = 0;
DWORD ret;
DWORD ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
return -1;
}
// Get table size
ret = GetIpForwardTable(NULL, &size, 0);
if (ret != ERROR_INSUFFICIENT_BUFFER) {
return -1;
}
table = (PMIB_IPFORWARDTABLE)malloc(size);
if (!table) return -1;
ret = GetIpForwardTable(table, &size, 0);
if (ret != NO_ERROR) {
free(table);
return -1;
}
// Delete all routes for this interface
for (DWORD i = 0; i < table->dwNumEntries; i++) {
if (table->table[i].dwForwardIfIndex == ifindex) {
DeleteIpForwardEntry(&table->table[i]);
}
}
free(table);
return 0;
}
#else
// BSD / macOS routing socket implementation
static int routing_socket_cmd(int ifindex, uint32_t network, uint8_t prefix_len, int cmd) {
int fd = socket(PF_ROUTE, SOCK_RAW, AF_INET);
if (fd < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to create routing socket: %s", strerror(errno));
return -1;
}
uint8_t buf[512];
memset(buf, 0, sizeof(buf));
struct rt_msghdr *rtm = (struct rt_msghdr *)buf;
rtm->rtm_msglen = sizeof(struct rt_msghdr);
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = cmd; // RTM_ADD or RTM_DELETE
rtm->rtm_index = ifindex;
rtm->rtm_pid = getpid();
rtm->rtm_addrs = RTA_DST | RTA_NETMASK | RTA_IFP;
rtm->rtm_flags = RTF_UP | RTF_GATEWAY | RTF_STATIC;
// Destination address (network)
struct sockaddr_in *sin = (struct sockaddr_in *)(rtm + 1);
sin->sin_family = AF_INET;
sin->sin_len = sizeof(struct sockaddr_in);
sin->sin_addr.s_addr = htonl(network);
rtm->rtm_msglen += sizeof(struct sockaddr_in);
// Netmask
sin++;
sin->sin_family = AF_INET;
sin->sin_len = sizeof(struct sockaddr_in);
sin->sin_addr.s_addr = prefix_to_netmask(prefix_len);
rtm->rtm_msglen += sizeof(struct sockaddr_in);
// Interface name
sin++;
struct sockaddr_dl *sdl = (struct sockaddr_dl *)sin;
sdl->sdl_family = AF_LINK;
sdl->sdl_index = ifindex;
sdl->sdl_len = sizeof(struct sockaddr_dl);
rtm->rtm_msglen += sizeof(struct sockaddr_dl);
if (write(fd, rtm, rtm->rtm_msglen) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to send routing message: %s", strerror(errno));
close(fd);
return -1;
}
// Read response
ssize_t n = read(fd, buf, sizeof(buf));
if (n >= sizeof(struct rt_msghdr)) {
if (rtm->rtm_errno != 0) {
errno = rtm->rtm_errno;
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Routing error: %s", strerror(errno));
close(fd);
return -1;
}
}
close(fd);
return 0;
}
// Fallback: use route command
static int route_cmd(const char *ifname, uint32_t network, uint8_t prefix_len, const char *cmd) {
char network_str[INET_ADDRSTRLEN];
struct in_addr addr;
addr.s_addr = htonl(network);
if (!inet_ntop(AF_INET, &addr, network_str, sizeof(network_str))) {
return -1;
}
char cmdline[256];
snprintf(cmdline, sizeof(cmdline), "route %s -net %s/%d -interface %s 2>/dev/null",
cmd, network_str, prefix_len, ifname);
int ret = system(cmdline);
return (ret == 0) ? 0 : -1;
}
int tun_route_add(const char *ifname, uint32_t network, uint8_t prefix_len) {
int ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Interface %s not found", ifname);
return -1;
}
// Try routing socket first
if (routing_socket_cmd(ifindex, network, prefix_len, RTM_ADD) == 0) {
return 0;
}
// Fallback to route command
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Routing socket failed, trying route command for %s", ifname);
return route_cmd(ifname, network, prefix_len, "add");
}
int tun_route_del(const char *ifname, uint32_t network, uint8_t prefix_len) {
int ifindex = if_nametoindex(ifname);
if (ifindex == 0) {
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Interface %s not found", ifname);
return -1;
}
// Try routing socket first
if (routing_socket_cmd(ifindex, network, prefix_len, RTM_DELETE) == 0) {
return 0;
}
// Fallback to route command
DEBUG_WARN(DEBUG_CATEGORY_TUN, "Routing socket failed, trying route command for %s", ifname);
return route_cmd(ifname, network, prefix_len, "delete");
}
int tun_route_flush(const char *ifname) {
// Use route command to flush routes - more reliable on BSD
char cmdline[256];
snprintf(cmdline, sizeof(cmdline),
"route -n show -interface %s 2>/dev/null | grep -v '^[[:space:]]*#' | "
"awk '/^[0-9]+\\./{print $1}' | while read r; do route delete -net $r 2>/dev/null; done",
ifname);
int ret = system(cmdline);
return (ret == 0) ? 0 : -1;
}
#endif
// Platform-independent functions
int tun_route_add_all(const char *ifname, struct CFG_ROUTE_ENTRY *routes) {
int count = 0;
struct CFG_ROUTE_ENTRY *entry = routes;
while (entry) {
uint32_t network = ntohl(entry->ip.addr.v4.s_addr);
if (entry->ip.family == AF_INET) {
if (tun_route_add(ifname, network, entry->netmask) == 0) {
char ip_str[INET_ADDRSTRLEN];
struct in_addr addr;
addr.s_addr = entry->ip.addr.v4.s_addr;
inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str));
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added route %s/%d via %s", ip_str, entry->netmask, ifname);
count++;
} else {
char ip_str[INET_ADDRSTRLEN];
struct in_addr addr;
addr.s_addr = entry->ip.addr.v4.s_addr;
inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str));
DEBUG_ERROR(DEBUG_CATEGORY_TUN, "Failed to add route %s/%d via %s", ip_str, entry->netmask, ifname);
}
}
entry = entry->next;
}
return count;
}

51
src/tun_route.h

@ -0,0 +1,51 @@
// tun_route.h - Cross-platform system routing table management
#ifndef TUN_ROUTE_H
#define TUN_ROUTE_H
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
struct CFG_ROUTE_ENTRY;
/**
* @brief Add system route for a subnet via TUN interface
* @param ifname Interface name (e.g., "tun0")
* @param network Network address in host byte order
* @param prefix_len Prefix length (1-32)
* @return 0 on success, -1 on error
*/
int tun_route_add(const char *ifname, uint32_t network, uint8_t prefix_len);
/**
* @brief Delete system route for a subnet via TUN interface
* @param ifname Interface name (e.g., "tun0")
* @param network Network address in host byte order
* @param prefix_len Prefix length (1-32)
* @return 0 on success, -1 on error
*/
int tun_route_del(const char *ifname, uint32_t network, uint8_t prefix_len);
/**
* @brief Delete all system routes via specified interface
* @param ifname Interface name (e.g., "tun0")
* @return 0 on success, -1 on error
*/
int tun_route_flush(const char *ifname);
/**
* @brief Add multiple routes from config entries
* @param ifname Interface name (e.g., "tun0")
* @param routes Linked list of route entries
* @return Number of routes added successfully
*/
int tun_route_add_all(const char *ifname, struct CFG_ROUTE_ENTRY *routes);
#ifdef __cplusplus
}
#endif
#endif /* TUN_ROUTE_H */

6
src/utun.conf

@ -11,9 +11,9 @@ my_public_key=
[routing]
allowed_subnet=10.0.0.0/24
allowed_subnet=10.22.0.0/16
allowed_subnet=10.23.0.0/16
route_subnet=10.0.0.0/24
route_subnet=10.22.0.0/16
route_subnet=10.23.0.0/16
my_subnet=10.23.5.0/24
my_subnet=10.23.6.0/24

12
src/utun_instance.c

@ -3,6 +3,7 @@
#include "config_parser.h"
#include "config_updater.h"
#include "tun_if.h"
#include "tun_route.h"
#include "route_lib.h"
#include "routing.h"
#include "route_bgp.h"
@ -100,6 +101,12 @@ static int instance_init_common(struct UTUN_INSTANCE* instance, struct UASYNC* u
return -1;
}
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN interface initialized: %s", instance->tun->ifname);
// Add system routes for route_subnets
if (config->route_subnets) {
int added = tun_route_add_all(instance->tun->ifname, config->route_subnets);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Added %d system routes for TUN interface", added);
}
} else {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "TUN initialization disabled - skipping TUN device setup");
instance->tun = NULL;
@ -219,6 +226,11 @@ void utun_instance_destroy(struct UTUN_INSTANCE *instance) {
// Cleanup TUN
if (instance->tun) {
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Closing TUN interface: %s", instance->tun->ifname);
// Flush all system routes for this interface
tun_route_flush(instance->tun->ifname);
DEBUG_INFO(DEBUG_CATEGORY_TUN, "Flushed system routes for %s", instance->tun->ifname);
tun_close(instance->tun);
instance->tun = NULL;
}

1
tests/Makefile.am

@ -72,6 +72,7 @@ ETCP_FULL_OBJS = \
$(top_builddir)/src/utun-route_bgp.o \
$(top_builddir)/src/utun-routing.o \
$(top_builddir)/src/utun-tun_if.o \
$(top_builddir)/src/utun-tun_route.o \
$(TUN_PLATFORM_OBJ) \
$(top_builddir)/src/utun-utun_instance.o \
$(ETCP_CORE_OBJS)

44
utun.conf

@ -1,44 +0,0 @@
[global]
tun_ip=10.0.0.1
mtu=1500 # MTU for all connections (0 = use default 1500)
control_ip=127.0.0.1
control_port=12345
net_debug=0
my_node_id=61be9d4cd3c60c2d
my_private_key=1313912e5d34768983b0e06530a48c77816d228a5b5605e1ab3dc443d107a3dc
my_public_key=dde6cec8a9023339a758f60883ef41534d24a1ffdc09bbb787a5c24ddfd891e3092461835a97d37944c681fc6b2c1f5acde8ad192f7d2cdc9920aa0d3ff78e99
[routing]
allowed_subnet=10.0.0.0/24
allowed_subnet=10.22.0.0/16
allowed_subnet=10.23.0.0/16
my_subnet=10.23.5.0/24
my_subnet=10.23.6.0/24
# секция server обязательна и у сервера и у клиента. это рабочий сокет.
# мои адреса и каналы
[server: lo0_test]
addr=127.0.0.1:1330
#so_mark=100
#netif=eth0
type=nat # public / nat / private
[server: lan1]
addr=192.168.29.117:1333
so_mark=100
netif=eth0
type=public # public / nat / private
[client: client_test1]
# линки
link=lo0_test:192.168.0.20:1234
#link=wired1_fast:1.2.3.4:1234
link=lan1:192.168.0.20:1234
#link=wireless_bkp:1.2.3.4:1234
keepalive=1
peer_public_key=deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbe

32
utun.conf.sample

@ -0,0 +1,32 @@
[global]
tun_ip=10.0.0.1
mtu=1500 # MTU for all connections (0 = use default 1500)
control_ip=127.0.0.1
control_port=12345
net_debug=0
my_node_id=
my_private_key=
my_public_key=
[routing]
route_subnet=10.0.0.0/24
route_subnet=10.23.0.0/16
#allowed_subnet=10.23.0.0/16
my_subnet=10.23.1.0/24
# секция server обязательна и у сервера и у клиента. это рабочий сокет.
# мои адреса и каналы
[server: lo0_test]
addr=127.0.0.1:1330
#so_mark=100
#netif=eth0
type=nat # public / nat / private
[server: lan1]
addr=192.168.29.117:1333
#so_mark=100
#netif=eth0
type=public # public / nat / private
Loading…
Cancel
Save