Просмотр исходного кода

feat(eoip): add dedicated Tunnel IP (CIDR) overlay configuration to EoIP modal, table and persistent interface binding

Proxmox Router 1 неделя назад
Родитель
Сommit
fccfc20590
3 измененных файлов с 55 добавлено и 10 удалено
  1. 27 0
      internal/vpn/eoip.go
  2. 8 0
      internal/web/static/app.js
  3. 20 10
      internal/web/static/index.html

+ 27 - 0
internal/vpn/eoip.go

@@ -11,6 +11,7 @@ import (
 	"os/exec"
 	"os/exec"
 	"path/filepath"
 	"path/filepath"
 	"strconv"
 	"strconv"
+	"strings"
 	"sync"
 	"sync"
 	"sync/atomic"
 	"sync/atomic"
 	"syscall"
 	"syscall"
@@ -24,6 +25,7 @@ type EoIPInstance struct {
 	TunnelID      int    `json:"tunnel_id"`      // 1 - 65535
 	TunnelID      int    `json:"tunnel_id"`      // 1 - 65535
 	RemoteAddress string `json:"remote_address"` // Remote MikroTik IPv4
 	RemoteAddress string `json:"remote_address"` // Remote MikroTik IPv4
 	LocalAddress  string `json:"local_address"`  // Local router IPv4 (optional)
 	LocalAddress  string `json:"local_address"`  // Local router IPv4 (optional)
+	IPAddress     string `json:"ip_address,omitempty"` // Overlay IP/CIDR (e.g. "172.16.33.254/30")
 	MACAddress    string `json:"mac_address"`    // e.g. "02:64:83:6b:46:44"
 	MACAddress    string `json:"mac_address"`    // e.g. "02:64:83:6b:46:44"
 	MTU           int    `json:"mtu"`            // default 1500
 	MTU           int    `json:"mtu"`            // default 1500
 	ClampMSS      bool   `json:"clamp_mss"`      // Clamp TCP MSS for PMTUD
 	ClampMSS      bool   `json:"clamp_mss"`      // Clamp TCP MSS for PMTUD
@@ -127,6 +129,12 @@ func (m *EoIPManager) Save(inst *EoIPInstance) error {
 		inst.ID = fmt.Sprintf("eoip-%d", time.Now().UnixNano())
 		inst.ID = fmt.Sprintf("eoip-%d", time.Now().UnixNano())
 	}
 	}
 
 
+	// Smart detection: if user put CIDR into LocalAddress by mistake, move it to IPAddress
+	if strings.Contains(inst.LocalAddress, "/") && inst.IPAddress == "" {
+		inst.IPAddress = inst.LocalAddress
+		inst.LocalAddress = ""
+	}
+
 	// Stop previous worker if exists
 	// Stop previous worker if exists
 	if w, exists := m.workers[inst.ID]; exists {
 	if w, exists := m.workers[inst.ID]; exists {
 		w.stop()
 		w.stop()
@@ -237,6 +245,20 @@ func (m *EoIPManager) updateStats(t *EoIPInstance) {
 	}
 	}
 	t.RxHuman = formatBytes(t.RxBytes)
 	t.RxHuman = formatBytes(t.RxBytes)
 	t.TxHuman = formatBytes(t.TxBytes)
 	t.TxHuman = formatBytes(t.TxBytes)
+
+	// Detect any bound IPv4 if not explicitly saved in JSON
+	if t.IPAddress == "" {
+		if ifi, err := net.InterfaceByName(t.Name); err == nil {
+			if addrs, err := ifi.Addrs(); err == nil {
+				for _, a := range addrs {
+					if ipNet, ok := a.(*net.IPNet); ok && ipNet.IP.To4() != nil {
+						t.IPAddress = ipNet.String()
+						break
+					}
+				}
+			}
+		}
+	}
 }
 }
 
 
 // -------------------------------------------------------------
 // -------------------------------------------------------------
@@ -293,6 +315,11 @@ func newEoIPTunnelWorker(inst *EoIPInstance) (*eoipTunnelWorker, error) {
 	_ = exec.Command("ip", "link", "set", inst.Name, "up").Run()
 	_ = exec.Command("ip", "link", "set", inst.Name, "up").Run()
 	_ = exec.Command("ip", "link", "set", peerName, "up").Run()
 	_ = exec.Command("ip", "link", "set", peerName, "up").Run()
 
 
+	// 4b. Assign Overlay IP address if specified
+	if inst.IPAddress != "" {
+		_ = exec.Command("ip", "addr", "add", inst.IPAddress, "dev", inst.Name).Run()
+	}
+
 	// 5. TCP MSS Clamping
 	// 5. TCP MSS Clamping
 	if inst.ClampMSS {
 	if inst.ClampMSS {
 		_ = exec.Command("iptables", "-t", "mangle", "-A", "FORWARD", "-o", inst.Name, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu").Run()
 		_ = exec.Command("iptables", "-t", "mangle", "-A", "FORWARD", "-o", inst.Name, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu").Run()

+ 8 - 0
internal/web/static/app.js

@@ -2045,6 +2045,9 @@ async function addInterfaceIP() {
       await fetchData(false);
       await fetchData(false);
       const iface = (state.interfaces || []).find(i => i.name === state.currentModalIface);
       const iface = (state.interfaces || []).find(i => i.name === state.currentModalIface);
       if (iface) renderModalIPs(iface);
       if (iface) renderModalIPs(iface);
+    } else {
+      const err = await res.json();
+      showToast('Error', err.error || err.message || 'Failed to add IP address', 'error');
     }
     }
   } catch (err) {
   } catch (err) {
     showToast('Error', err.message, 'error');
     showToast('Error', err.message, 'error');
@@ -3500,6 +3503,7 @@ function renderEoIPTunnels() {
       <td class="py-2.5 px-3 text-center">${renderMikroTikFlag(t.running, t.disabled)}</td>
       <td class="py-2.5 px-3 text-center">${renderMikroTikFlag(t.running, t.disabled)}</td>
       <td class="py-2.5 px-3 font-bold text-white font-mono">${t.name}</td>
       <td class="py-2.5 px-3 font-bold text-white font-mono">${t.name}</td>
       <td class="py-2.5 px-3 font-bold text-emerald-400 font-mono">${t.tunnel_id}</td>
       <td class="py-2.5 px-3 font-bold text-emerald-400 font-mono">${t.tunnel_id}</td>
+      <td class="py-2.5 px-3 font-semibold text-emerald-400 font-mono">${t.ip_address || '<span class="text-slate-500 font-normal">Unassigned</span>'}</td>
       <td class="py-2.5 px-3 text-cyan-300 font-mono">${t.remote_address}</td>
       <td class="py-2.5 px-3 text-cyan-300 font-mono">${t.remote_address}</td>
       <td class="py-2.5 px-3 text-slate-400 font-mono">${t.local_address || '(any)'}</td>
       <td class="py-2.5 px-3 text-slate-400 font-mono">${t.local_address || '(any)'}</td>
       <td class="py-2.5 px-3 text-slate-300 font-mono text-[11px]">${t.mac_address || '-'}</td>
       <td class="py-2.5 px-3 text-slate-300 font-mono text-[11px]">${t.mac_address || '-'}</td>
@@ -3525,6 +3529,7 @@ function openAddEoIPModal() {
   document.getElementById('eoip-modal-name').value = `eoip-tunnel${(state.eoipTunnels || []).length + 1}`;
   document.getElementById('eoip-modal-name').value = `eoip-tunnel${(state.eoipTunnels || []).length + 1}`;
   document.getElementById('eoip-modal-tunnelid').value = (state.eoipTunnels || []).length + 10;
   document.getElementById('eoip-modal-tunnelid').value = (state.eoipTunnels || []).length + 10;
   document.getElementById('eoip-modal-remote').value = '';
   document.getElementById('eoip-modal-remote').value = '';
+  document.getElementById('eoip-modal-ip').value = '';
   document.getElementById('eoip-modal-local').value = '';
   document.getElementById('eoip-modal-local').value = '';
   document.getElementById('eoip-modal-mac').value = '';
   document.getElementById('eoip-modal-mac').value = '';
   document.getElementById('eoip-modal-mtu').value = '1500';
   document.getElementById('eoip-modal-mtu').value = '1500';
@@ -3542,6 +3547,7 @@ function openEditEoIPModal(id) {
   document.getElementById('eoip-modal-name').value = t.name;
   document.getElementById('eoip-modal-name').value = t.name;
   document.getElementById('eoip-modal-tunnelid').value = t.tunnel_id;
   document.getElementById('eoip-modal-tunnelid').value = t.tunnel_id;
   document.getElementById('eoip-modal-remote').value = t.remote_address;
   document.getElementById('eoip-modal-remote').value = t.remote_address;
+  document.getElementById('eoip-modal-ip').value = t.ip_address || '';
   document.getElementById('eoip-modal-local').value = t.local_address || '';
   document.getElementById('eoip-modal-local').value = t.local_address || '';
   document.getElementById('eoip-modal-mac').value = t.mac_address || '';
   document.getElementById('eoip-modal-mac').value = t.mac_address || '';
   document.getElementById('eoip-modal-mtu').value = t.mtu || 1500;
   document.getElementById('eoip-modal-mtu').value = t.mtu || 1500;
@@ -3561,6 +3567,7 @@ async function submitSaveEoIP(e) {
   const name = document.getElementById('eoip-modal-name').value.trim();
   const name = document.getElementById('eoip-modal-name').value.trim();
   const tunnelId = parseInt(document.getElementById('eoip-modal-tunnelid').value);
   const tunnelId = parseInt(document.getElementById('eoip-modal-tunnelid').value);
   const remote = document.getElementById('eoip-modal-remote').value.trim();
   const remote = document.getElementById('eoip-modal-remote').value.trim();
+  const ipAddress = document.getElementById('eoip-modal-ip').value.trim();
   const local = document.getElementById('eoip-modal-local').value.trim();
   const local = document.getElementById('eoip-modal-local').value.trim();
   const mac = document.getElementById('eoip-modal-mac').value.trim();
   const mac = document.getElementById('eoip-modal-mac').value.trim();
   const mtu = parseInt(document.getElementById('eoip-modal-mtu').value) || 1500;
   const mtu = parseInt(document.getElementById('eoip-modal-mtu').value) || 1500;
@@ -3577,6 +3584,7 @@ async function submitSaveEoIP(e) {
         name,
         name,
         tunnel_id: tunnelId,
         tunnel_id: tunnelId,
         remote_address: remote,
         remote_address: remote,
+        ip_address: ipAddress,
         local_address: local,
         local_address: local,
         mac_address: mac,
         mac_address: mac,
         mtu,
         mtu,

+ 20 - 10
internal/web/static/index.html

@@ -1138,6 +1138,7 @@
                     <th class="py-2.5 px-3 w-12 text-center" title="Flags: R=Running, X=Disabled">Flag</th>
                     <th class="py-2.5 px-3 w-12 text-center" title="Flags: R=Running, X=Disabled">Flag</th>
                     <th class="py-2.5 px-3">Name</th>
                     <th class="py-2.5 px-3">Name</th>
                     <th class="py-2.5 px-3">Tunnel ID</th>
                     <th class="py-2.5 px-3">Tunnel ID</th>
+                    <th class="py-2.5 px-3">Tunnel IP</th>
                     <th class="py-2.5 px-3">Remote Address</th>
                     <th class="py-2.5 px-3">Remote Address</th>
                     <th class="py-2.5 px-3">Local Address</th>
                     <th class="py-2.5 px-3">Local Address</th>
                     <th class="py-2.5 px-3">MAC Address</th>
                     <th class="py-2.5 px-3">MAC Address</th>
@@ -2845,29 +2846,38 @@
 
 
         <div class="grid grid-cols-2 gap-4">
         <div class="grid grid-cols-2 gap-4">
           <div>
           <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Remote Address (IPv4) *</label>
-            <input type="text" id="eoip-modal-remote" placeholder="198.51.100.2" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-emerald-500">
+            <label class="block font-semibold text-slate-300 mb-1.5">Remote Address (Remote Router IP) *</label>
+            <input type="text" id="eoip-modal-remote" placeholder="e.g. 10.2.145.253" required class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-emerald-500">
+            <span class="text-[10px] text-slate-400">Underlay IP of the remote MikroTik router</span>
           </div>
           </div>
           <div>
           <div>
-            <label class="block font-semibold text-slate-300 mb-1.5">Local Address (Optional)</label>
-            <input type="text" id="eoip-modal-local" placeholder="e.g. 192.168.131.56" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-emerald-500">
+            <label class="block font-semibold text-slate-300 mb-1.5">Tunnel IP / Subnet (CIDR)</label>
+            <input type="text" id="eoip-modal-ip" placeholder="e.g. 172.16.33.254/30" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-emerald-500">
+            <span class="text-[10px] text-slate-400">Overlay IPv4 assigned directly to tunnel (/30, /24)</span>
           </div>
           </div>
         </div>
         </div>
 
 
         <div class="grid grid-cols-2 gap-4">
         <div class="grid grid-cols-2 gap-4">
+          <div>
+            <label class="block font-semibold text-slate-300 mb-1.5">Underlay Local IP (GRE Source)</label>
+            <input type="text" id="eoip-modal-local" placeholder="e.g. 10.42.36.5" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono focus:outline-none focus:border-emerald-500">
+            <span class="text-[10px] text-slate-400">Local router IP for outer GRE packets</span>
+          </div>
           <div>
           <div>
             <label class="block font-semibold text-slate-300 mb-1.5">MAC Address (Auto if empty)</label>
             <label class="block font-semibold text-slate-300 mb-1.5">MAC Address (Auto if empty)</label>
             <input type="text" id="eoip-modal-mac" placeholder="02:64:xx:xx:xx:xx" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
             <input type="text" id="eoip-modal-mac" placeholder="02:64:xx:xx:xx:xx" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
           </div>
           </div>
+        </div>
+
+        <div class="grid grid-cols-2 gap-4">
           <div>
           <div>
             <label class="block font-semibold text-slate-300 mb-1.5">MTU</label>
             <label class="block font-semibold text-slate-300 mb-1.5">MTU</label>
             <input type="number" id="eoip-modal-mtu" value="1500" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
             <input type="number" id="eoip-modal-mtu" value="1500" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white font-mono">
           </div>
           </div>
-        </div>
-
-        <div>
-          <label class="block font-semibold text-slate-300 mb-1.5">Comment</label>
-          <input type="text" id="eoip-modal-comment" placeholder="Bridge to Office Branch" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white">
+          <div>
+            <label class="block font-semibold text-slate-300 mb-1.5">Comment</label>
+            <input type="text" id="eoip-modal-comment" placeholder="Bridge to Office Branch" class="w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 text-white">
+          </div>
         </div>
         </div>
 
 
         <div class="flex items-center justify-between pt-1">
         <div class="flex items-center justify-between pt-1">
@@ -3451,6 +3461,6 @@
     </div>
     </div>
   </div>
   </div>
 
 
-  <script src="app.js?v=2.5"></script>
+  <script src="app.js?v=2.6"></script>
 </body>
 </body>
 </html>
 </html>