From d38c4770ac309f9d6caa4612ae9dae765ddffcf2 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 6 Apr 2025 00:08:17 +0800 Subject: [PATCH] feat: Add VXLAN over WireGuard support with site automation - Enabled VXLAN over L3 via WireGuard tunnel; - Standardized Ansible role structure for `vpn-overlay`; - Integrated systemd service management via `vxlan-setup.service`; - Configured per-site VXLAN overlay in `config/sit/vpn-overlay.yaml`; --- .../vhosts/common/files/install-packages.sh | 2 +- .../vpn-overlay/vxlan/site/defaults/main.yml | 7 ++ .../vxlan/site/files/setup_sit_vxlan.sh | 76 +++++++++++++++++++ .../vpn-overlay/vxlan/site/tasks/main.yml | 54 +++++++++++++ .../site/templates/vxlan-setup.service.j2 | 12 +++ ansible/playbooks/vpn-overlay.yaml | 8 ++ config/{sites.yaml => sit/vpn-overlay.yaml} | 35 +++++---- 7 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml create mode 100644 ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 create mode 100644 ansible/playbooks/vpn-overlay.yaml rename config/{sites.yaml => sit/vpn-overlay.yaml} (56%) diff --git a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh index 3ff7ff5b..e3967b6e 100644 --- a/ansible/playbooks/roles/vhosts/common/files/install-packages.sh +++ b/ansible/playbooks/roles/vhosts/common/files/install-packages.sh @@ -2,4 +2,4 @@ export DEBIAN_FRONTEND=noninteractive sudo apt-get update -sudo apt-get install -y vim inetutils-ping +sudo apt-get install -y vim iputils-ping rsync diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml new file mode 100644 index 00000000..6d9fb4f3 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# Default VXLAN settings for site nodes +vxlan_dev_if: wg0 +vxlan_vni: 100 +vxlan_cidr_suffix: 16 +overlay_config_path: config/sit/vpn-overlay.yaml + diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh new file mode 100644 index 00000000..b1dc3619 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/files/setup_sit_vxlan.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# 安全增强版 VXLAN Overlay 脚本(支持 wg0 作为通道设备) +# 用法: ./setup_sit_vxlan.sh [cidr_suffix] [vxlan_id] [mtu] + +set -e + +DEV_IF="$1" +LOCAL_IP="$2" +REMOTE_IP="$3" +BRIDGE_IP="$4" +CIDR_SUFFIX="${5:-16}" +VNI="${6:-100}" +MTU="${7:-1400}" + +if [[ -z "$DEV_IF" || -z "$LOCAL_IP" || -z "$REMOTE_IP" || -z "$BRIDGE_IP" ]]; then + echo "Usage: $0 [cidr_suffix] [vxlan_id] [mtu]" + exit 1 +fi + +VXLAN_IF="vxlan${VNI}" +BR_IF="br0" +BRIDGE_CIDR="${BRIDGE_IP}/${CIDR_SUFFIX}" +SUBNET="$(echo "$BRIDGE_IP" | cut -d. -f1-2).0.0/${CIDR_SUFFIX}" + +# 自动判断 dev 是否能用于 VXLAN(需支持广播) +function is_vxlan_dev_usable() { + [[ -d "/sys/class/net/$1" ]] && grep -q "broadcast" "/sys/class/net/$1/flags" +} + +echo "🔍 检查 $DEV_IF 是否可用于 VXLAN..." +USE_DEV_PARAM=true +if ! is_vxlan_dev_usable "$DEV_IF"; then + echo "⚠️ $DEV_IF 不支持广播,将省略 dev 参数(通过路由走隧道)" + USE_DEV_PARAM=false +fi + +# 清理旧接口 +for iface in "$VXLAN_IF" "$BR_IF"; do + ip link show "$iface" &>/dev/null && ip link set "$iface" down && ip link del "$iface" +done + +# 创建 VXLAN 接口 +echo "🛠️ 创建 VXLAN 接口 $VXLAN_IF ..." +if $USE_DEV_PARAM; then + ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" dev "$DEV_IF" +else + ip link add "$VXLAN_IF" type vxlan id "$VNI" dstport 4789 local "$LOCAL_IP" remote "$REMOTE_IP" +fi +ip link set "$VXLAN_IF" mtu "$MTU" +ip link set "$VXLAN_IF" up + +# 创建 br0 桥 +ip link add "$BR_IF" type bridge +ip link set "$BR_IF" mtu "$MTU" +ip link set "$VXLAN_IF" master "$BR_IF" +ip link set "$BR_IF" up + +# 配置 IP +ip addr add "$BRIDGE_CIDR" dev "$BR_IF" + +# 启用转发 + SNAT(仅主机出网时) +sysctl -w net.ipv4.ip_forward=1 +iptables -t nat -C POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE 2>/dev/null || \ +iptables -t nat -A POSTROUTING -s "$SUBNET" -o "$DEV_IF" -j MASQUERADE + +# 尝试激活邻居学习 +REMOTE_BR_IP="172.16.0.$(( $(echo "$BRIDGE_IP" | awk -F. '{print $4}') == 2 ? 3 : 2 ))" +ping -c 1 "$REMOTE_BR_IP" || true + +# 展示结果 +echo "" +echo "✅ VXLAN Overlay 已建立" +echo " - bridge: $BR_IF ($BRIDGE_CIDR)" +echo " - vxlan: $VXLAN_IF ($LOCAL_IP ⇄ $REMOTE_IP, id=$VNI, mtu=$MTU)" +echo " - SNAT: $SUBNET → $DEV_IF" + diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml new file mode 100644 index 00000000..691a3aaa --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/tasks/main.yml @@ -0,0 +1,54 @@ +--- +- name: Load site overlay config + set_fact: + overlay_data: "{{ lookup('file', overlay_config_path) | from_yaml }}" + +- name: Select current site from config + set_fact: + current_site: "{{ overlay_data.sites | selectattr('name', 'equalto', inventory_hostname) | list | first }}" + +- name: Fail if no matching site found + fail: + msg: "当前主机 {{ inventory_hostname }} 未在 config 中定义" + when: current_site is not defined + +- name: Select first hub as default + set_fact: + selected_hub: "{{ overlay_data.hubs[0] }}" + +- name: Set VXLAN parameters + set_fact: + vxlan_dev_if: "{{ current_site.interface}}" + vxlan_local_ip: "{{ current_site.local_ip }}" + vxlan_remote_ip: "{{ current_site.remote_ip }}" + vxlan_br_ip: "{{ current_site.br_ip }}" + vxlan_cidr_suffix: "{{ overlay_data.vxlan_cidr_suffix | default(16) }}" + vxlan_vni: "{{ overlay_data.vxlan_id | default(100) }}" + +- name: 使用 rsync 分发 VXLAN 脚本 + synchronize: + src: "files/setup_sit_vxlan.sh" + dest: /tmp/setup_sit_vxlan.sh + mode: push + +- name: 移动脚本并赋权到 /usr/local/bin + shell: | + mv /tmp/setup_sit_vxlan.sh /usr/local/bin/setup_sit_vxlan.sh + chmod +x /usr/local/bin/setup_sit_vxlan.sh + become: true + +- name: Render systemd unit for VXLAN + template: + src: vxlan-setup.service.j2 + dest: /etc/systemd/system/vxlan-setup.service + mode: '0644' + +- name: Reload systemd daemon + systemd: + daemon_reload: true + +- name: Enable and start VXLAN overlay setup + systemd: + name: vxlan-setup + enabled: true + state: started diff --git a/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 new file mode 100644 index 00000000..d9313f02 --- /dev/null +++ b/ansible/playbooks/roles/vhosts/vpn-overlay/vxlan/site/templates/vxlan-setup.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=VXLAN Overlay Setup for {{ inventory_hostname }} +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/setup_sit_vxlan.sh {{ vxlan_dev_if }} {{ vxlan_local_ip }} {{ vxlan_remote_ip }} {{ vxlan_br_ip }} {{ vxlan_cidr_suffix }} {{ vxlan_vni }} +RemainAfterExit=true + +[Install] +WantedBy=multi-user.target diff --git a/ansible/playbooks/vpn-overlay.yaml b/ansible/playbooks/vpn-overlay.yaml new file mode 100644 index 00000000..1a64ec7f --- /dev/null +++ b/ansible/playbooks/vpn-overlay.yaml @@ -0,0 +1,8 @@ +- name: Run infrastructure setup + hosts: all + become: yes + gather_facts: yes + vars: + overlay_config_path: "{{ playbook_dir }}/../../config/sit/vpn-overlay.yaml" + roles: + - vhosts/vpn-overlay/vxlan/site diff --git a/config/sites.yaml b/config/sit/vpn-overlay.yaml similarity index 56% rename from config/sites.yaml rename to config/sit/vpn-overlay.yaml index 89d96e65..1957ff1d 100644 --- a/config/sites.yaml +++ b/config/sit/vpn-overlay.yaml @@ -1,16 +1,16 @@ # 基础网络参数 -wg_network: 10.100.0.0/24 -bridge_network: 172.16.0.0/16 +wg_network: 172.16.0.0/24 +bridge_network: 10.253.0.0/16 vxlan_id: 100 hub_port: 51820 # 全局功能开关 features: - enable_gretap: true # 是否开启 gretap 桥接(站点接入 Hub) + enable_vxlan_between_sits: true # 是否开启 vxlan 桥接(站点接入 Hub) enable_vxlan_between_hubs: true # 是否开启 Hub 之间的 VXLAN Mesh - enable_vless: true # 是否通过 VLESS 中转 WG 流量 - enable_multi_hub: true # 是否支持多 Hub 架构(false 则为单 Hub star 架构) - only_wireguard: false # 若为 true,仅使用 WireGuard 点对点,忽略 gretap/vxlan + enable_vless: true # 是否通过 VLESS 中转 WG 流量 + enable_multi_hub: true # 是否支持多 Hub 架构(false 则为单 Hub star 架构) + only_wireguard: false # 若为 true,仅使用 WireGuard 点对点,忽略 gretap/vxlan # WireGuard Hub 节点配置 hubs: @@ -26,24 +26,23 @@ hubs: # 各个站点定义 sites: - - name: site_a - wg_ip: 10.100.0.2 - br_ip: 172.16.0.2 - hub: hub1 - public_ip: a.example.com + - name: master-1 + interface: ens5 + br_ip: 10.253.253.2 + local_ip: 10.255.0.200 + remote_ip: 10.255.0.100 vless: - enabled: true - endpoint: vless-a.example.com + enabled: false uuid: "11111111-1111-1111-1111-111111111111" transport: ws tls: true path: /wg - - name: site_b - wg_ip: 10.100.0.3 - br_ip: 172.16.0.3 - hub: hub1 - public_ip: b.example.com + - name: slave-1 + interface: ens5 + br_ip: 10.253.253.3 + local_ip: 10.255.0.100 + remote_ip: 10.255.0.200 vless: enabled: false # 此节点不通过 VLESS,直接访问 Hub