Seperate multiple public IPs to different devices

Hello mikrotik community,

I have an interesting topic I need help with.
I’m trying to use multiple public IPs which are obtained from the same WAN interface (ether1). The idea is that my main network has to use one public IP. And the other Public IP is for a server where other people can connect to. All Public IPs I obtain are dynamic.

So my question is, how can I do that? I tried using MacVLAN. Ether1 has dhcp client enabled, macvlan1 also has dhcp client enabled. Macvlan1 is under ether1. Ether1 is the main WAN port and all devices are using ether1 port public IP.

I’m thinking that this can be resolved somehow by using routing tables? But I could not get it to work. The other method maybe by using the firewall itself and some connection marking?

The incoming WAN connection gives me 4 dynamic public IPs but I only need 2.

Kind regards,
Martin

As long as you can pull additional IP address (e.g. using MACVLAN interface), you can use that additional IP address simply for NAT … both dst-nat (i.e. public clients can connect to secondary_address:port and those connections get NATed to internal server) and src-nat (for server’s outgoing connections … if you want to do it). The only gotcha is how to handle potential changes of public IP address (if you have to use DHCP client to get one, then it probably can change from time to time). You can use DHCP client script which adds/removes NAT rules if/when IP address gets changed …

If those additional addresses are static (even if ISP mandates use of DHCP to deliver reserved IP addresses, you could simply statically add those IP addresses to WAN interface and be done with it.

Using routing tables is not necessary since all public IP addresses will be bound to same physical WAN interface (and using same/none VLAN ID) and upstream gateway will be (most probably) the same for all IP addresses.

Hi again,

Did some tinkering with AI but seems like this solution doesn’t work. Atleast for me. The AI of course is a little outdated.


DHCP Client Configuration
First, set up two DHCP clients for your WAN interface:

/ip dhcp-client
add interface=ether1 use-peer-dns=yes add-default-route=yes
add interface=macvlan use-peer-dns=no add-default-route=no

The first client will be used for your primary connection, and the second for the specific computer.
IP Address Lists
Create two address lists to dynamically track your public IPs:

/ip firewall address-list
add list=public_ip1
add list=public_ip2

DHCP Script
Create a script to update the address lists when DHCP leases change:

/system script
add name=update_public_ips source={
    :local dhcp1 [/ip dhcp-client get [find interface=ether1] address]
    :local dhcp2 [/ip dhcp-client get [find interface=macvlan] address]
    /ip firewall address-list set [find list=public_ip1] address=$dhcp1
    /ip firewall address-list set [find list=public_ip2] address=$dhcp2
}

Add this script to DHCP lease events:

/system scheduler
add interval=1m name=update_public_ips on-event="/system script run update_public_ips"

NAT Configuration
Modify the NAT rules to use the address lists:

/ip firewall nat
add chain=srcnat out-interface=ether1 action=masquerade src-address=192.168.1.0/24 to-addresses=[/ip firewall address-list get [find list=public_ip1] address]
add chain=srcnat out-interface=macvlan action=src-nat src-address=<specific_computer_ip> to-addresses=[/ip firewall address-list get [find list=public_ip2] address]

Firewall Mangle Configuration
The mangle rules remain largely the same:

/ip firewall mangle
add chain=prerouting src-address=<specific_computer_ip> action=mark-connection new-connection-mark=second_ip_conn
add chain=prerouting connection-mark=second_ip_conn action=mark-routing new-routing-mark=second_ip_route

Routing Configuration
For the routing, we’ll use a script to update the route when the IP changes:

/system script
add name=update_second_ip_route source={
    :local newIP [/ip firewall address-list get [find list=public_ip2] address]
    /ip route set [find routing-mark=second_ip_route] gateway=$newIP
}

Initially set up the route:

/ip route
add dst-address=0.0.0.0/0 gateway=[/ip firewall address-list get [find list=public_ip2] address] routing-mark=second_ip_route

Add this script to run periodically:

/system scheduler
add interval=1m name=update_second_ip_route on-event="/system script run update_second_ip_route"

This setup will dynamically update your configuration as your public IPs change. Remember to replace <specific_computer_ip> with the actual IP of the computer that needs to use the second public IP.
Also, ensure your firewall rules are set up to allow the necessary traffic. This configuration assumes that both DHCP clients are on the same physical interface (ether1). If they’re on separate interfaces, you’ll need to adjust the interface names accordingly.

Looks like this idea is not working on my network. I get multiple IP’s from my ISP from ether1 but when trying to ping from that macvlan1 interface for example 8.8.8.8 then it gets timed out. The macvlan1 has been added to the WAN interface list so no fw rule is blocking it.

So my conclusion is that yes, I get multiple IPs but the internet is not working from those other ones.

A lot depends on how paranoid your ISP is - they may check whether you send the packets from an IP address from the same MAC address from which you have requested the lease of that IP address.

So first of all I would check that the dhcp client attached to the macvlan inteface has a valid lease, and then run /tool sniffer quick ip-address=the.one.assigned.to.macvlan and try to access that address from outside - the easiest way is to put it to the address field of a browser in your phone that is connected to mobile data, not the local WiFi network.

If you can see something to come in (it should be TCP SYN packets coming to port 80, first on ether1 and then on the macvlan interface), at least the inbound direction works.

If so, the next thing to deal with is that the routing does not care about source address unless you force it to do so. So do the following:
/routing/table/add name=for-macvlan fib
/ip/route/add routing-table=for-macvlan gateway=ip.of.isp.gw%macvlan-interface-name
/routing/rule/add src-address=the.one.assigned.to.macvlan action=lookup-only-in-table table=for-macvlan

This will make sure that packets you send from the macvlan’s IP address will leave with macvlan’s MAC address as source.

And then try ping 8.8.8.8 src-address=the.one.assigned.to.macvlan . If that succeeds, it makes sense to move on to delivering the traffic for that address further to the server.

Thank you for the answer. It looks like it works. I can ping 8.8.8.8 and other IPs from the macvlan public IP with this conf.
Now I need to figure out why the config didn’t work. Sounds like my skill issue :smiley:

“Skill” and “experience with the oddities of various ISPs” are not the same thing :slight_smile:

I’d suggest to disable just the routing rule and try the same ping again - if that prevents the pings from getting responded, we can be sure that the ISP indeed checks the correspondence between the source IP address and source MAC address and take it as a matter of fact for further steps.

Hi all. Looks like I got it to work. Almost all of this is made with ideas from this forum, trial and error but mostly ChatGPT.

/routing table
add fib name=for-macvlan

/ip dhcp-client
add interface=ether1 use-peer-dns=no
add add-default-route=no interface=macvlan1 use-peer-dns=no use-peer-ntp=no

# The IP that comes from the script (down below) automatically so it can be whatever for the start: ip_that_comes_from_script
# You can add 0.0.0.0 or something different
add action=masquerade chain=srcnat comment=Masquerade out-interface-list=WAN
add action=src-nat chain=srcnat comment="NAT to other Public IP" \
    out-interface=macvlan1 src-address-list=to_macvlan to-addresses=\
    ip_that_comes_from_script

# Gateway is taken automatically with the script down below
/ip route
add comment=macvlan disabled=no distance=1 dst-address=0.0.0.0/0 gateway=\
    gateway_from_script routing-table=for-macvlan scope=30 suppress-hw-offload=no \
    target-scope=10

/routing rule
add action=lookup-only-in-table comment=Server disabled=no src-address=\
    device_ip_for_wan2 table=for-macvlan

# Those scripts are made with ChatGPT. I am not that smart to create scripts like that
# But I had multiple days trial and error and at the moment they seem to work fine
/system script
add dont-require-permissions=no name=update_public_ips owner=user policy=\
    ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="#\
    \n_Fetch DHCP client addresses for ether1 and macvlan1\r\
    \n:local dhcp1 [/ip dhcp-client get [find interface=\"ether1\"] address];\
    \r\
    \n:local dhcp2 [/ip dhcp-client get [find interface=\"macvlan1\"] address]\
    ;\r\
    \n\r\
    \n# Strip the subnet mask from the DHCP client IPs (if needed)\r\
    \n:local dhcp1IP [:pick \$dhcp1 0 [:find \$dhcp1 \"/\"]];\r\
    \n:local dhcp2IP [:pick \$dhcp2 0 [:find \$dhcp2 \"/\"]];\r\
    \n\r\
    \n# Log the current DHCP IPs\r\
    \n#:log info \"Current DHCP IP for ether1: \$dhcp1IP\";\r\
    \n#:log info \"Current DHCP IP for macvlan1: \$dhcp2IP\";\r\
    \n\r\
    \n# Retrieve the current IPs in the address list\r\
    \n:local currentPublicIP1 [/ip firewall address-list get [find list=\"publ\
    ic_ip1\"] address];\r\
    \n:local currentPublicIP2 [/ip firewall address-list get [find list=\"publ\
    ic_ip2\"] address];\r\
    \n\r\
    \n# Log the current IPs in the address list\r\
    \n#:log info \"Current IP in public_ip1: \$currentPublicIP1\";\r\
    \n#:log info \"Current IP in public_ip2: \$currentPublicIP2\";\r\
    \n\r\
    \n# Update public_ip1 if different\r\
    \n:if (\$dhcp1IP != \$currentPublicIP1) do={\r\
    \n    /ip firewall address-list set [find list=\"public_ip1\"] address=\"\
    \$dhcp1IP\";\r\
    \n    :log info \"Updated public_ip1 with IP \$dhcp1IP\";\r\
    \n} else={\r\
    \n    :log info \"public_ip1 is already up-to-date with IP \$currentPublic\
    IP1\";\r\
    \n}\r\
    \n\r\
    \n# Update public_ip2 if different\r\
    \n:if (\$dhcp2IP != \$currentPublicIP2) do={\r\
    \n    /ip firewall address-list set [find list=\"public_ip2\"] address=\"\
    \$dhcp2IP\";\r\
    \n    :log info \"Updated public_ip2 with IP \$dhcp2IP\";\r\
    \n} else={\r\
    \n    :log info \"public_ip2 is already up-to-date with IP \$currentPublic\
    IP2\";\r\
    \n}\r\
    \n\r\
    \n# Get the current NAT IP from the NAT rule\r\
    \n:local natRuleId [/ip firewall nat find where comment=\"NAT to other Pub\
    lic IP\"];\r\
    \n:local currentNatIP [/ip firewall nat get \$natRuleId to-addresses];\r\
    \n\r\
    \n# Log the current NAT IP\r\
    \n#:log info \"Current NAT IP: \$currentNatIP\";\r\
    \n\r\
    \n# Update NAT rule if different\r\
    \n:if (\$dhcp2IP != \$currentNatIP) do={\r\
    \n    :if ([:len \$natRuleId] > 0) do={\r\
    \n        /ip firewall nat set \$natRuleId to-addresses=\"\$dhcp2IP\";\r\
    \n        :log info \"Updated existing NAT rule with new IP \$dhcp2IP\";\r\
    \n    } else={\r\
    \n        /ip firewall nat add chain=srcnat action=src-nat to-addresses=\"\
    \$dhcp2IP\" src-address-list=to_macvlan out-interface=macvlan1 comment=\"N\
    AT to other Public IP\";\r\
    \n        :log info \"Created new NAT rule with IP \$dhcp2IP\";\r\
    \n    }\r\
    \n} else={\r\
    \n    :log info \"NAT rule is already up-to-date with IP \$currentNatIP\";\
    \r\
    \n}"
add dont-require-permissions=no name=update_second_ip_route owner=user \
    policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
    source="# Fetch DHCP client information for macvlan1\r\
    \n:local dhcpClient [/ip dhcp-client find interface=\"macvlan1\"]\r\
    \n\r\
    \n# Check if DHCP client is found\r\
    \n:if ([:len \$dhcpClient] = 0) do={\r\
    \n    :log error \"DHCP client for macvlan1 not found.\"\r\
    \n    :error \"DHCP client for macvlan1 not found.\"\r\
    \n}\r\
    \n\r\
    \n# Extract the gateway from the DHCP client information\r\
    \n:local gatewayAddress [/ip dhcp-client get \$dhcpClient gateway]\r\
    \n\r\
    \n# Ensure gatewayAddress is not empty\r\
    \n:if (\$gatewayAddress = \"\") do={\r\
    \n    :log error \"Gateway address is empty for DHCP client on macvlan1.\"\
    \r\
    \n    :error \"Gateway address is empty for DHCP client on macvlan1.\"\r\
    \n}\r\
    \n\r\
    \n:log info \"Retrieved gateway address from DHCP client for macvlan1: \$g\
    atewayAddress\"\r\
    \n\r\
    \n# Find the route with comment \"macvlan\"\r\
    \n:local routeId [/ip route find where comment=\"macvlan\"]\r\
    \n\r\
    \n:if ([:len \$routeId] > 0) do={\r\
    \n    :log info \"Found route ID: \$routeId\"\r\
    \n    # Update the route with the new gateway address\r\
    \n    /ip route set \$routeId gateway=\$gatewayAddress\r\
    \n    :log info \"Updated route with new gateway address \$gatewayAddress\
    \"\r\
    \n} else={\r\
    \n    :log error \"No route found with comment 'macvlan'\"\r\
    \n}\r\
    \n"

# I put 5 minutes for my setup but it can be every 30 seconds also
# The script doesn't update the values if the IP's are already the same
/system scheduler
add interval=5m name=update_public_ips_scheduler on-event=\
    "/system script run update_public_ips" policy=\
    ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
    start-date=2024-09-02 start-time=18:00:00
add interval=5m name=update_second_ip_route_scheduler on-event=\
    "/system script run update_second_ip_route" policy=\
    ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon \
    start-date=2024-09-02 start-time=18:00:00

I hope I put everything important here.

Thank you for the help who participated and happy tinkering!

There is a script item of the dhcp client, which is invoked each time the DHCP asignment changes (address is lost, address is obtained, address is changed); it is not invoked if the lease is renewed without any change. So instead of scheduling the scripts for a periodical run, you can just set this script item of the dhcp client to the name of the script to be run. In the documentation, there is a list of ephemeral global variables that are made available to the script.

That’s a good point. No idea why I didn’t think of that. Thank you!
Does it also update the IP for example if the router has been offline for some time and get’s a new IP?
I would think it works like that.

If the router was offline for long enough that the lease has expired (or if the interface to which the DHCP client is attached went down), a new lease is requested rather than the previous one being renewed, and therefore the script does get invoked.