Quick Guide: Bidirectional 1:1 NAT (SNAT, DNAT) Setup w/ working example

Intro
Setting up bidirectional 1:1 NAT in RouterOS allows you to create static mappings between external and internal IP addresses. This configuration is useful when you need multiple internal (LAN) devices to be accessible via specific external (WAN) IPs. There is a special twist though, since in this particular setup all internal devices are using the same IP address (this was a mandatory requirements since we were not able to change the IP addresses on any of those devices).

This quick guide covers the essential RouterOS commands needed to properly configure dst-nat and src-nat rules for complete bi-directional communication. With these settings, traffic to your designated external IPs will be properly forwarded to the corresponding internal devices, and those internal devices will appear to use their mapped external IPs when connecting outbound.

Note: All credit goes to helpful and friendly forum user lurker888 located at: https://forum.mikrotik.com/memberlist.php?mode=viewprofile&u=213255 - thank you again fren for taking the time and patience the explain everything to me!!
If anyone wants to follow along on how this setup has been created step-by-step you can find the original forum post here: 1:1 NAT / DNAT configuration help

We are currently using this setup for communicating with 12x PLC (all with hard-coded identical IP addresses) for IIoT / data acquisition.

Here is an overview image:

While IP addresses 192.168.2.20 were a given, we have chosen 192.168.2.180 and 172.29.10.0/24 addresses arbitrarily.

Requirements

The starting point for all this was really only the fact that each PLC has fixed IP address 192.168.2.20; with that, we could not use a switch but had to implement 1:1 NAT. Added benefit is that PLC networks are separated from each other and we can also implement a firewall between external/internal traffic. For consistency reasons we also decided to use the same "gateway" address 192.168.2.180 on each etherN (N>1, since ether1 = WAN/external) which at first made things a bit more complicated (each PLC wanted to ping 192.168.2.180, but solved).

Configuration step-by-step

Interface Organization
We start with organising the interfaces. While list with nameWAN is not used later in the configuration, I think it is still useful to have it (consistency).

# Create interface lists for better management
/interface list
add name=PLC-PORTS
add name=WAN

# Add interfaces to lists
/interface list member
add interface=ether1 list=WAN
add interface=ether2 list=PLC-PORTS
add interface=ether3 list=PLC-PORTS
add interface=ether4 list=PLC-PORTS

IP Configuration
We will configure the IP addresses for the WAN interface and the PLC interfaces. The WAN interface will have a primary IP address, and each PLC will have a virtual IP address on the same subnet. The PLC interfaces will be configured with the same internal IP address.

Notes:

  • Initially I had the idea of using /32 subnet mask for the PLCs because we are only dealing and need to connect to this one IP address, but lurker88 recommended against that, so we are using /24 instead.
  • Having same IP address 192.168.2.180 on each ether1/ether2/... interface is not a problem, since they are not routed in the main routing table but instead in their specific routing table. This is accomplished by policy-routing (see next section).
# WAN interface (Edge device connection)
/ip address
add address=172.29.10.1/24 interface=ether1 comment="Edge Network Primary IP"
# Add virtual IPs for each PLC on the WAN interface
add address=172.29.10.102/24 interface=ether1 comment="PLC1 External IP"
add address=172.29.10.103/24 interface=ether1 comment="PLC2 External IP"
add address=172.29.10.104/24 interface=ether1 comment="PLC3 External IP"
# Configure IP addresses for each PLC interface - directly using Ethernet ports
add address=192.168.2.180/24 interface=ether2 comment="PLC1 Network"
add address=192.168.2.180/24 interface=ether3 comment="PLC2 Network"
add address=192.168.2.180/24 interface=ether4 comment="PLC3 Network"

Routing Tables

A separate routing table for each PLC is needed; this allows us to route traffic to the correct PLC based on the destination IP address.

/routing table
add disabled=no fib name=to-plc1
add disabled=no fib name=to-plc2
add disabled=no fib name=to-plc3

/routing rule
add action=lookup-only-in-table routing-mark=to-plc1 table=to-plc1
add action=lookup-only-in-table routing-mark=to-plc2 table=to-plc2
add action=lookup-only-in-table routing-mark=to-plc3 table=to-plc3

Connection Marking for Policy Routing

Here are two versions, first one works, but will not allow ping back from the PLCs to the router. The second one is a bit more complicated, but allows ping back from the PLCs to the router.

first one is missing connection and routing marks on traffic coming from the PLCs to the router. This is not a problem for most applications, but if you need to ping the router from the PLCs, you will need to add connection and routing marks.

/ip firewall mangle
# Mark inbound traffic from WAN to PLCs with routing marks directly
add chain=prerouting in-interface=ether1 dst-address=172.29.10.102 action=mark-routing new-routing-mark=to-plc1 comment="Mark route to PLC1"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.103 action=mark-routing new-routing-mark=to-plc2 comment="Mark route to PLC3"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.104 action=mark-routing new-routing-mark=to-plc3 comment="Mark route to PLC2"

second one has connection and routing marks on traffic coming from the PLCs to the router.

/ip firewall mangle
# Mark inbound traffic from WAN to PLCs with routing marks directly
add chain=prerouting in-interface=ether1 dst-address=172.29.10.102 action=mark-routing new-routing-mark=to-plc1 comment="Route to PLC1"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.103 action=mark-routing new-routing-mark=to-plc2 comment="Route to PLC2"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.104 action=mark-routing new-routing-mark=to-plc3 comment="Route to PLC3"

# Mark connections based on initiator
add chain=prerouting connection-mark=no-mark in-interface=ether1 action=mark-connection new-connection-mark=from-mgmt comment="Mark connection from MGMT side"
add chain=prerouting connection-mark=no-mark in-interface=ether2 action=mark-connection new-connection-mark=from-plc1 comment="Mark connection from PLC1"
add chain=prerouting connection-mark=no-mark in-interface=ether3 action=mark-connection new-connection-mark=from-plc2 comment="Mark connection from PLC2"
add chain=prerouting connection-mark=no-mark in-interface=ether4 action=mark-connection new-connection-mark=from-plc3 comment="Mark connection from PLC3"

# Route return traffic from router to PLCs (where connection-mark=from-plc)
add chain=output connection-mark=from-plc1 action=mark-routing new-routing-mark=to-plc1 comment="Route to PLC1"
add chain=output connection-mark=from-plc2 action=mark-routing new-routing-mark=to-plc2 comment="Route to PLC2"
add chain=output connection-mark=from-plc3 action=mark-routing new-routing-mark=to-plc3 comment="Route to PLC3"

Policy-Based Routing Rules

The routing rules are used to direct traffic based on the connection marks set in the previous step. Each PLC will have its own routing table, and the rules will ensure that traffic is routed correctly.

Note: Policy routes for the entire PLC subnet (/24), not just the PLC (/32)

/ip route
add dst-address=192.168.2.0/24 gateway=ether2 routing-table=to-plc1 comment="Route to PLC1"
add dst-address=192.168.2.0/24 gateway=ether3 routing-table=to-plc2 comment="Route to PLC2"
add dst-address=192.168.2.0/24 gateway=ether4 routing-table=to-plc3 comment="Route to PLC3"

NAT Configuration

Finally, this is where the magic happens. The NAT rules are used to translate the IP addresses between the external and internal networks. The destination NAT rules will forward traffic from the external IPs to the internal PLCs, and the source NAT rule (applies to all interfaces in list PLC-PORTS) will ensure that replies from the PLCs appear to come from the external IPs.

# Destination NAT
/ip firewall nat
add chain=dstnat in-interface=ether1 dst-address=172.29.10.102 action=dst-nat to-addresses=192.168.2.20 comment="PLC1 Inbound"
add chain=dstnat in-interface=ether1 dst-address=172.29.10.103 action=dst-nat to-addresses=192.168.2.20 comment="PLC2 Inbound"
add chain=dstnat in-interface=ether1 dst-address=172.29.10.104 action=dst-nat to-addresses=192.168.2.20 comment="PLC3 Inbound"

# Source NAT for replies (makes router appear as 192.168.2.180 to PLCs)
add chain=srcnat out-interface-list=PLC-PORTS action=src-nat to-addresses=192.168.2.180 comment="Source NAT to PLCs"

# might be default but just in case
/ip firewall connection tracking set enabled=yes

Final configuration

Here is the final configuration:

## /export
## /export verbose
## /system reboot
## /system reset-configuration

# Identity for better management
/system identity set name="PLC-Gateway"

#######################################
# Interface Organization
#######################################

# Create interface lists for better management
/interface list
add name=PLC-PORTS
add name=WAN

# Add interfaces to lists
/interface list member
add interface=ether1 list=WAN
add interface=ether2 list=PLC-PORTS
add interface=ether3 list=PLC-PORTS
add interface=ether4 list=PLC-PORTS

#######################################
# IP Configuration
#######################################

# WAN interface (Edge device connection)
/ip address
add address=172.29.10.1/24 interface=ether1 comment="Edge Network Primary IP"
# Add virtual IPs for each PLC on the WAN interface
add address=172.29.10.102/24 interface=ether1 comment="PLC1 External IP"
add address=172.29.10.103/24 interface=ether1 comment="PLC2 External IP"
add address=172.29.10.104/24 interface=ether1 comment="PLC3 External IP"
# Configure IP addresses for each PLC interface - directly using Ethernet ports
add address=192.168.2.180/24 interface=ether2 comment="PLC1 Network"
add address=192.168.2.180/24 interface=ether3 comment="PLC2 Network"
add address=192.168.2.180/24 interface=ether4 comment="PLC3 Network"

#######################################
# Create Routing Tables
#######################################

/routing table
add disabled=no fib name=to-plc1
add disabled=no fib name=to-plc2
add disabled=no fib name=to-plc3

#######################################
# Create Routing Rules
#######################################

/routing rule
add action=lookup-only-in-table routing-mark=to-plc1 table=to-plc1
add action=lookup-only-in-table routing-mark=to-plc2 table=to-plc2
add action=lookup-only-in-table routing-mark=to-plc3 table=to-plc3

#######################################
# Connection Marking for Policy Routing
#######################################

/ip firewall mangle
# Mark inbound traffic from WAN to PLCs with routing marks directly
add chain=prerouting in-interface=ether1 dst-address=172.29.10.102 action=mark-routing new-routing-mark=to-plc1 comment="Route to PLC1"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.103 action=mark-routing new-routing-mark=to-plc2 comment="Route to PLC2"
add chain=prerouting in-interface=ether1 dst-address=172.29.10.104 action=mark-routing new-routing-mark=to-plc3 comment="Route to PLC3"

# Mark connections based on initiator
add chain=prerouting connection-mark=no-mark in-interface=ether1 action=mark-connection new-connection-mark=from-mgmt comment="Mark connection from MGMT side"
add chain=prerouting connection-mark=no-mark in-interface=ether2 action=mark-connection new-connection-mark=from-plc1 comment="Mark connection from PLC1"
add chain=prerouting connection-mark=no-mark in-interface=ether3 action=mark-connection new-connection-mark=from-plc2 comment="Mark connection from PLC2"
add chain=prerouting connection-mark=no-mark in-interface=ether4 action=mark-connection new-connection-mark=from-plc3 comment="Mark connection from PLC3"

# Route return traffic from router to PLCs (where connection-mark=from-plc)
add chain=output connection-mark=from-plc1 action=mark-routing new-routing-mark=to-plc1 comment="Route to PLC1"
add chain=output connection-mark=from-plc2 action=mark-routing new-routing-mark=to-plc2 comment="Route to PLC2"
add chain=output connection-mark=from-plc3 action=mark-routing new-routing-mark=to-plc3 comment="Route to PLC3"

#######################################
# Policy-Based Routing Rules
#######################################

# Policy routes for the entire PLC subnet (/24), not just the PLC (/32)
/ip route
add dst-address=192.168.2.0/24 gateway=ether2 routing-table=to-plc1 comment="Route to PLC1"
add dst-address=192.168.2.0/24 gateway=ether3 routing-table=to-plc2 comment="Route to PLC2"
add dst-address=192.168.2.0/24 gateway=ether4 routing-table=to-plc3 comment="Route to PLC3"

#######################################
# NAT Configuration - Inbound & Reply Traffic
#######################################

# Destination NAT
/ip firewall nat
add chain=dstnat in-interface=ether1 dst-address=172.29.10.102 action=dst-nat to-addresses=192.168.2.20 comment="PLC1 Inbound"
add chain=dstnat in-interface=ether1 dst-address=172.29.10.103 action=dst-nat to-addresses=192.168.2.20 comment="PLC2 Inbound"
add chain=dstnat in-interface=ether1 dst-address=172.29.10.104 action=dst-nat to-addresses=192.168.2.20 comment="PLC3 Inbound"

# Source NAT for replies (makes router appear as 192.168.2.180 to PLCs)
add chain=srcnat out-interface-list=PLC-PORTS action=src-nat to-addresses=192.168.2.180 comment="Source NAT to PLCs"

# might be default but just in case
/ip firewall connection tracking set enabled=yes

Fin.

Update 2026-02-23

There is also a completely self-contained HTML code below. It is suitable for offline usage since all styling/CSS, code/Javascript, and HTML is in one single file. But - as always - review this code or throw it into an LLM to chek security / safety before using it!

Here is how it looks:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MikroTik PLC Gateway Config Generator</title>
    <style>
        * { box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 1100px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
        h1 { color: #333; margin-bottom: 5px; }
        .subtitle { color: #666; margin-bottom: 20px; }
        .section { background: white; padding: 20px; border-radius: 8px; margin-bottom: 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
        .section h2 { margin-top: 0; font-size: 16px; color: #444; border-bottom: 1px solid #eee; padding-bottom: 10px; }
        .form-row { display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 10px; }
        .form-group { flex: 1; min-width: 180px; }
        label { display: block; font-size: 13px; color: #555; margin-bottom: 4px; }
        input { width: 100%; padding: 8px 10px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; }
        input:focus { outline: none; border-color: #007bff; }
        table { width: 100%; border-collapse: collapse; font-size: 13px; }
        th, td { padding: 8px 6px; text-align: left; border-bottom: 1px solid #eee; }
        th { background: #f8f9fa; font-weight: 600; color: #555; }
        td input { padding: 6px 8px; font-size: 13px; }
        .iface-col { width: 80px; }
        .ip-col { width: 140px; }
        .action-col { width: 50px; text-align: center; }
        .btn-remove { background: #dc3545; color: white; border: none; border-radius: 4px; width: 28px; height: 28px; cursor: pointer; font-size: 18px; display: flex; align-items: center; justify-content: center; }
        .btn-remove:hover { background: #c82333; }
        .btn-remove:disabled { background: #ccc; cursor: not-allowed; }
        .add-row { margin-top: 10px; }
        .btn-add { background: #28a745; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-size: 14px; }
        .btn-add:hover { background: #218838; }
        textarea { width: 100%; height: 400px; font-family: "SF Mono", Monaco, "Courier New", monospace; font-size: 12px; padding: 12px; border: 1px solid #ddd; border-radius: 4px; background: #1e1e1e; color: #d4d4d4; resize: vertical; }
        .buttons { display: flex; gap: 10px; margin-bottom: 15px; }
        button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: 500; }
        .btn-primary { background: #007bff; color: white; }
        .btn-primary:hover { background: #0056b3; }
        .btn-secondary { background: #6c757d; color: white; }
        .btn-secondary:hover { background: #545b62; }
        .btn-generate { background: #007bff; color: white; font-size: 15px; padding: 12px 30px; }
        .btn-generate:hover { background: #0056b3; }
        .toast { position: fixed; bottom: 20px; right: 20px; background: #28a745; color: white; padding: 12px 20px; border-radius: 4px; display: none; }
        .output-buttons { display: flex; gap: 10px; margin-top: 10px; }
        .hint { font-size: 12px; color: #888; margin-top: 5px; }
    </style>
</head>
<body>
    <h1>MikroTik PLC Gateway Config Generator</h1>
    <p class="subtitle">Generate 1:1 NAT configuration for PLCs with identical IP addresses</p>

    <div class="section">
        <h2>Global Settings</h2>
        <div class="form-row">
            <div class="form-group">
                <label>Router Name</label>
                <input type="text" id="routerName" value="PLC-Gateway">
            </div>
            <div class="form-group">
                <label>WAN IP (ether1)</label>
                <input type="text" id="wanIP" value="172.29.10.1">
            </div>
            <div class="form-group" style="flex: 0.3; min-width: 80px;">
                <label>Subnet</label>
                <input type="text" id="subnet" value="24">
            </div>
        </div>
    </div>

    <div class="section">
        <h2>PLC Configuration</h2>
        <table id="plcTable">
            <thead>
                <tr>
                    <th class="iface-col">Interface</th>
                    <th class="ip-col">External IP</th>
                    <th class="ip-col">Internal IP (Gateway)</th>
                    <th class="ip-col">PLC IP</th>
                    <th class="action-col"></th>
                </tr>
            </thead>
            <tbody id="plcBody"></tbody>
        </table>
        <div class="add-row">
            <button class="btn-add" onclick="addPLC()">+ Add PLC</button>
        </div>
        <p class="hint">External IP = virtual IP on WAN for this PLC | Internal IP (Gateway) = router's IP on this PLC's network | PLC IP = actual IP of the PLC device</p>
    </div>

    <div class="section">
        <h2>Generate Configuration</h2>
        <div class="buttons">
            <button class="btn-generate" onclick="generate()">Generate Config</button>
        </div>
        <textarea id="output" readonly placeholder="Click 'Generate Config' to create the RouterOS script..."></textarea>
        <div class="output-buttons">
            <button class="btn-primary" onclick="copyConfig()">Copy to Clipboard</button>
            <button class="btn-secondary" onclick="downloadConfig()">Download .rsc</button>
        </div>
    </div>

    <div class="toast" id="toast">Copied to clipboard!</div>

    <script>
        let plcData = [];

        function initDefaults() {
            // Start with 3 PLCs with sensible defaults
            plcData = [
                { extIP: '172.29.10.102', plcIP: '192.168.2.20', gwIP: '192.168.2.180' },
                { extIP: '172.29.10.103', plcIP: '192.168.2.20', gwIP: '192.168.2.180' },
                { extIP: '172.29.10.104', plcIP: '192.168.2.20', gwIP: '192.168.2.180' }
            ];
            renderTable();
        }

        function renderTable() {
            const tbody = document.getElementById('plcBody');
            tbody.innerHTML = plcData.map((p, i) => `
                <tr>
                    <td class="iface-col">ether${i + 2}</td>
                    <td class="ip-col"><input type="text" value="${p.extIP}" onchange="updatePLC(${i}, 'extIP', this.value)"></td>
                    <td class="ip-col"><input type="text" value="${p.gwIP}" onchange="updatePLC(${i}, 'gwIP', this.value)"></td>
                    <td class="ip-col"><input type="text" value="${p.plcIP}" onchange="updatePLC(${i}, 'plcIP', this.value)"></td>
                    <td class="action-col"><button class="btn-remove" onclick="removePLC(${i})" ${plcData.length <= 1 ? 'disabled' : ''}>−</button></td>
                </tr>
            `).join('');
        }

        function updatePLC(index, field, value) {
            plcData[index][field] = value;
        }

        function addPLC() {
            // Auto-increment based on last entry
            const last = plcData[plcData.length - 1];
            const lastExtParts = last.extIP.split('.').map(Number);
            lastExtParts[3]++;
            const newExtIP = lastExtParts.join('.');

            plcData.push({
                extIP: newExtIP,
                plcIP: last.plcIP,
                gwIP: last.gwIP
            });
            renderTable();
        }

        function removePLC(index) {
            if (plcData.length > 1) {
                plcData.splice(index, 1);
                renderTable();
            }
        }

        function getSubnetAddress(ip) {
            const parts = ip.split('.').map(Number);
            parts[3] = 0;
            return parts.join('.') + '/24';
        }

        function generate() {
            const routerName = document.getElementById('routerName').value || 'PLC-Gateway';
            const wanIP = document.getElementById('wanIP').value || '172.29.10.1';
            const subnet = document.getElementById('subnet').value || '24';

            // Build PLC list with interface assignments
            const plcs = plcData.map((p, i) => ({
                num: i + 1,
                iface: `ether${i + 2}`,
                extIP: p.extIP,
                plcIP: p.plcIP,
                gwIP: p.gwIP
            }));

            // Padding helper
            const pad = (s, len) => s + ' '.repeat(Math.max(0, len - s.length));

            let config = `## /export
## /export verbose
## /system reboot
## /system reset-configuration

# Identity for better management
/system identity set name="${routerName}"

#######################################
# Interface Organization
#######################################

# Create interface lists for better management
/interface list
add name=PLC-PORTS
add name=WAN

# Add interfaces to lists
/interface list member
add interface=ether1  list=WAN
`;
            plcs.forEach(p => {
                config += `add interface=${pad(p.iface, 7)} list=PLC-PORTS\n`;
            });

            config += `
#######################################
# IP Configuration
#######################################

# WAN interface (Edge device connection)
/ip address
add address=${wanIP}/${subnet} interface=ether1 comment="Edge Network Primary IP"
# Add virtual IPs for each PLC on the WAN interface
`;
            plcs.forEach(p => {
                config += `add address=${p.extIP}/${subnet} interface=ether1 comment="PLC${p.num} External IP"\n`;
            });

            config += `# Configure IP addresses for each PLC interface - directly using Ethernet ports\n`;
            plcs.forEach(p => {
                config += `add address=${p.gwIP}/24 interface=${pad(p.iface, 7)} comment="PLC${p.num} Network"\n`;
            });

            config += `
#######################################
# Create Routing Tables
#######################################

/routing table
`;
            plcs.forEach(p => {
                config += `add disabled=no fib name=to-plc${p.num}\n`;
            });

            config += `
#######################################
# Create Routing Rules
#######################################

/routing rule
`;
            plcs.forEach(p => {
                config += `add action=lookup-only-in-table routing-mark=to-plc${p.num} table=to-plc${p.num}\n`;
            });

            config += `
#######################################
# Connection Marking for Policy Routing
#######################################

/ip firewall mangle
# Mark connections based on the external IP being accessed
`;
            plcs.forEach(p => {
                config += `add chain=prerouting in-interface=ether1 dst-address=${p.extIP} action=mark-connection new-connection-mark=plc${p.num}-conn comment="Mark PLC${p.num} Connections"\n`;
            });

            config += `
# Convert connection marks to routing marks for policy routing - ONLY for packets coming from WAN
`;
            plcs.forEach(p => {
                config += `add chain=prerouting in-interface=ether1 connection-mark=plc${p.num}-conn action=mark-routing new-routing-mark=to-plc${p.num} comment="Mark route to PLC${p.num}"\n`;
            });

            config += `
# Mark connections for return traffic from the PLCs to the WAN/Router
add chain=prerouting in-interface=ether1  connection-mark=no-mark action=mark-connection new-connection-mark=from-mgmt  comment="Mark connection from MGMT side"
`;
            plcs.forEach(p => {
                config += `add chain=prerouting in-interface=${pad(p.iface, 7)} connection-mark=no-mark action=mark-connection new-connection-mark=from-plc${p.num} comment="Mark connection from PLC${p.num}"\n`;
            });

            config += `\n`;
            plcs.forEach(p => {
                config += `add chain=output connection-mark=from-plc${p.num} action=mark-routing new-routing-mark=to-plc${p.num} comment="Mark route to PLC${p.num}"\n`;
            });

            config += `
#######################################
# Policy-Based Routing Rules
#######################################

# Policy routes for the entire PLC subnet (/24), not just the PLC (/32)
/ip route
`;
            plcs.forEach(p => {
                const plcSubnet = getSubnetAddress(p.plcIP);
                config += `add dst-address=${plcSubnet} gateway=${pad(p.iface, 7)} routing-table=to-plc${p.num} comment="Route to PLC${p.num}"\n`;
            });

            config += `
#######################################
# NAT Configuration - Inbound & Reply Traffic
#######################################

# Destination NAT
/ip firewall nat
`;
            plcs.forEach(p => {
                config += `add chain=dstnat in-interface=ether1 dst-address=${p.extIP} action=dst-nat to-addresses=${p.plcIP} comment="PLC${p.num} Inbound"\n`;
            });

            // Collect unique gateway IPs for SRCNAT
            const uniqueGWs = [...new Set(plcs.map(p => p.gwIP))];

            config += `
# Source NAT for replies (makes router appear as gateway IP to PLCs)
`;
            if (uniqueGWs.length === 1) {
                config += `add chain=srcnat out-interface-list=PLC-PORTS action=src-nat to-addresses=${uniqueGWs[0]} comment="Source NAT to PLCs"\n`;
            } else {
                // Different gateways per interface - need per-interface SRCNAT
                plcs.forEach(p => {
                    config += `add chain=srcnat out-interface=${p.iface} action=src-nat to-addresses=${p.gwIP} comment="Source NAT to PLC${p.num}"\n`;
                });
            }

            config += `
# might be default but just in case
/ip firewall connection tracking set enabled=yes
`;

            document.getElementById('output').value = config;
        }

        function copyConfig() {
            const output = document.getElementById('output');
            if (!output.value) {
                alert('Please generate the config first.');
                return;
            }
            output.select();
            document.execCommand('copy');
            const toast = document.getElementById('toast');
            toast.style.display = 'block';
            setTimeout(() => toast.style.display = 'none', 2000);
        }

        function downloadConfig() {
            const config = document.getElementById('output').value;
            if (!config) {
                alert('Please generate the config first.');
                return;
            }
            const routerName = document.getElementById('routerName').value || 'PLC-Gateway';
            const blob = new Blob([config], { type: 'text/plain' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `${routerName.toLowerCase().replace(/[^a-z0-9]/g, '-')}-config.rsc`;
            a.click();
            URL.revokeObjectURL(url);
        }

        // Initialize with defaults
        initDefaults();
    </script>
</body>
</html>
3 Likes