Skip to content
Feb 15 / Greg

Mikrotik IPSec Tunnel/VPN When Both Sides Have Dynamic IPs/DHCP

At first glance, one would think this is impossible. It is NOT impossible, thanks to some scripting and a couple of free services. This will work for straight IPSec tunnels, PPTP tunnels, IPIP tunnels or even IPIP tunnels encrypted with IPSec :)

Step 1 is to figure out what our public IP is and a method to share it with the remote site. We are going to be using dns-o-matic. This is a free service from opendns that allows you to update multiple different dynamic DNS services via a single interface. I’m using dyndns.org for this example. In a nutshell dyndns.org allows you to update a publicly available DNS entry that is a subdomain of dyndns.org. In our example we will use gregsowell-siteA.dyndns.org and gregsowell-siteB.dyndns.org. So, we need a method to update our DNS entry…a SCRIPT!

Mikrotik Dynamic DNS Update Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# DNSoMatic automatic DNS updates
# User account info of DNSoMatic
:global maticuser "user"
:global maticpass "password"
:global matichost "Yourhost"
# No more changes need
 
:global previousIP
 
# Print values for debug
:log info "DNSoMatic: Updating dynamic IP on DNS for host $matichost"
:log info "DNSoMatic: User $maticuser y Pass $maticpass"
:log info "DNSoMatic: Last IP $previousIP"
 
# get the current IP address from the internet (in case of double-nat)
/tool fetch mode=http address="checkip.dyndns.org" src-path="/" dst-path="/dyndns.checkip.html"
:local result [/file get dyndns.checkip.html contents]
 
# parse the current IP result
:local resultLen [:len $result]
:local startLoc [:find $result ": " -1]
:set startLoc ($startLoc + 2)
:local endLoc [:find $result "</body>" -1]
:global currentIP [:pick $result $startLoc $endLoc]
:log info "DNSoMatic: IP actual $currentIP"
 
# Touching the string passed to fetch command on "src-path" option
:local str "/nic/update?hostname=$matichost&myip=$currentIP&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG"
 
:if ($currentIP != $previousIP) do={
:log info "DNSoMatic: Update need"
:set previousIP $currentIP
:log info "DNSoMatic: Sending update $currentIP"
:log info [ :put [/tool fetch host=MT user=$maticuser password=$maticpass mode=http address="updates.dnsomatic.com" src-path=$str dst-path=$matichost]]
:log info "DNSoMatic: Host $matichost updated on DNSoMatic with IP $currentIP"
}  else={
:log info "DNSoMatic: Previous IP $previousIP and current $currentIP equal, no update need"
}

In order for this script to work correctly, you need to update the dns-o-matic infomation at the top. You will also need to configure DNS servers on your Mikrotik…how else will it resolve the URLs ;)

1
2
/ip dns
set primary-dns=8.8.8.8 secondary-dns=4.2.2.2

Once you get your script in, you will need to schedule it to run at whatever interval you prefer. I use a 10 minute interval.

1
2
3
/system scheduler
add comment="" disabled=no interval=10m name=dynamic-dns-schedule on-event=dynamic-dns-script \
    start-date=jan/01/1970 start-time=00:00:01

Dynamic IPs on both sides with IPSec

Dynamic IPSec

SiteA:

Setup Dynamic Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/system script
add name=dynamic-dns-script policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive source="\
    # User account info of DNSoMatic\r\
    \n:global maticuser \"user\"\r\
    \n:global maticpass \"password\"\r\
    \n:global matichost \"gregsowell-sitea.dyndns.org\"\r\
    \n# No more changes need\r\
    \n\r\
    \n:global previousIP\r\
    \n\r\
    \n# Print values for debug\r\
    \n:log info \"DNSoMatic: Updating dynamic IP on DNS for host \$matichost\"\
    \r\
    \n:log info \"DNSoMatic: User \$maticuser y Pass \$maticpass\"\r\
    \n:log info \"DNSoMatic: Last IP \$previousIP\"\r\
    \n\r\
    \n# get the current IP address from the internet (in case of double-nat)\r\
    \n/tool fetch mode=http address=\"checkip.dyndns.org\" src-path=\"/\" dst-\
    path=\"/dyndns.checkip.html\"\r\
    \n:local result [/file get dyndns.checkip.html contents]\r\
    \n\r\
    \n# parse the current IP result\r\
    \n:local resultLen [:len \$result]\r\
    \n:local startLoc [:find \$result \": \" -1]\r\
    \n:set startLoc (\$startLoc + 2)\r\
    \n:local endLoc [:find \$result \"</body>\" -1]\r\
    \n:global currentIP [:pick \$result \$startLoc \$endLoc]\r\
    \n:log info \"DNSoMatic: IP actual \$currentIP\"\r\
    \n\r\
    \n# Touching the string passed to fetch command on \"src-path\" option\r\
    \n:local str \"/nic/update\?hostname=\$matichost&myip=\$currentIP&wildcard\
    =NOCHG&mx=NOCHG&backmx=NOCHG\"\r\
    \n\r\
    \n:if (\$currentIP != \$previousIP) do={\r\
    \n:log info \"DNSoMatic: Update need\"\r\
    \n:set previousIP \$currentIP\r\
    \n:log info \"DNSoMatic: Sending update \$currentIP\"\r\
    \n:log info [ :put [/tool fetch host=MT user=\$maticuser password=\$maticp\
    ass mode=http address=\"updates.dnsomatic.com\" src-path=\$str dst-path=\$\
    matichost]]\r\
    \n:log info \"DNSoMatic: Host \$matichost updated on DNSoMatic with IP \$c\
    urrentIP\"\r\
    \n}  else={\r\
    \n:log info \"DNSoMatic: Previous IP \$previousIP and current \$currentIP \
    equal, no update need\"\r\
    \n}"
 
/system scheduler
add comment="" disabled=no interval=10m name=dynamic-dns-schedule on-event=dynamic-dns-script \
    start-date=jan/01/1970 start-time=00:00:01

Setup NAT Bypass

1
2
3
4
5
/ip firewall nat
add action=accept chain=srcnat comment="NAT bypass" disabled=no dst-address=\
    192.168.0.0/16 out-interface=ether1
add action=masquerade chain=srcnat comment="default PAT" disabled=no out-interface=\
    ether1

Setup IPSec Peer

1
2
3
4
5
6
/ip ipsec peer
add address=2.2.2.2/32:500 auth-method=pre-shared-key dh-group=modp1024 \
    disabled=no dpd-interval=disable-dpd dpd-maximum-failures=1 \
    enc-algorithm=3des exchange-mode=main generate-policy=no hash-algorithm=\
    md5 lifebytes=0 lifetime=1d nat-traversal=no proposal-check=obey secret=\
    test send-initial-contact=yes

Setup IPSec Policy

1
2
3
4
5
/ip ipsec policy
add action=encrypt disabled=no dst-address=192.168.2.0/24:any \
    ipsec-protocols=esp level=require priority=0 proposal=default protocol=\
    all sa-dst-address=2.2.2.2 sa-src-address=1.1.1.1 src-address=\
    192.168.1.0/24:any tunnel=yes

Now that we have the basics configured, I’m sure you noticed that I put IP addresses in the IPSec peer and policy. The whole point here is that we are running our public side via DHCP, so how does this benefit us? As it is now, it doesn’t. We need another script to update our peer and policy in the event of an IP change.

Your peers and policies are numbered from 0 up. This list is a static list that can be referenced, for our update. The number entry is located right after the word set. In the below scripts, be sure to update it to the proper peer number and policy number. You can figure out their numbers by issuing print commands from a terminal:

1
2
/ip ipsec peer print
/ip ipsec policy print

You can see that the script resolves the IP address for siteA and siteB, then sets the entries as they should be.
Peer/Policy Update Script

1
2
3
4
:global LocalSite [:resolve gregsowell-siteA.dyndns.org]
:global RemoteSite [:resolve gregsowell-siteb.dyndns.org]
/ip ipsec policy set 0 sa-dst-address=$RemoteSite sa-src-address=$LocalSite
/ip ipsec peer set 0 address="$RemoteSite/32:500"

Peer/Policy Update Script – Copy and paste Version

1
2
3
4
5
6
7
8
/system script
add name=dynamic-router-update policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive source="\
    :global LocalSite [:resolve gregsowell-siteA.dyndns.org]\r\
    \n:global RemoteSite [:resolve gregsowell-siteb.dyndns.org]\r\
    \n/ip ipsec policy set 0 sa-dst-address=\$RemoteSite sa-src-address=\$Loca\
    lSite\r\
    \n/ip ipsec peer set 0 address=\"\$RemoteSite/32:500\""

You can either create a new schedule to run the peer/policy update, or you can just add the script to your existing schedule, which is what I recommend.

1
2
3
4
5
6
/system scheduler
add comment="" disabled=no interval=10m name=dynamic-dns-schedule on-event=\
    "dynamic-dns-script\r\
    \ndynamic-router-update" policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive \
    start-date=jan/01/1970 start-time=00:00:01

Site B should configure the same, only in reverse order for the IP addresses.

IPIP with IPSec

IPIP Tunnel with a Cherry on Top

SiteA:

The dynamic script and scheduler is the same as above.

Setup IPIP Tunnel Interface

1
2
3
/interface ipip
add comment="" disabled=no local-address=1.1.1.1 mtu=1480 name=ipip1 \
    remote-address=2.2.2.2

Setup IPSec Peer

1
2
3
4
5
6
/ip ipsec peer
add address=2.2.2.2/32:500 auth-method=pre-shared-key dh-group=modp1024 \
    disabled=no dpd-interval=disable-dpd dpd-maximum-failures=1 \
    enc-algorithm=3des exchange-mode=main generate-policy=no hash-algorithm=\
    md5 lifebytes=0 lifetime=1d nat-traversal=no proposal-check=obey secret=\
    test send-initial-contact=yes

Setup IPSec Policy

1
2
3
4
5
/ip ipsec policy
add action=encrypt disabled=no dst-address=2.2.2.2/32:any ipsec-protocols=esp \
    level=require priority=0 proposal=default protocol=ip-encap \
    sa-dst-address=2.2.2.2 sa-src-address=1.1.1.1 src-address=1.1.1.1/32:any \
    tunnel=no

We’re going to add an additional step to the update script to take into account the new entries for our policy and for the IPIP interface

You can see that the script resolves the IP address for siteA and siteB, then sets the entries as they should be.
Peer/Policy Update Script

1
2
3
4
5
:global LocalSite [:resolve gregsowell-siteA.dyndns.org]
:global RemoteSite [:resolve gregsowell-siteb.dyndns.org]
/ip ipsec policy set 0 sa-dst-address=$RemoteSite sa-src-address=$LocalSite dst-address="$RemoteSite/32:any" src-address="$LocalSite/32:any"
/ip ipsec peer set 0 address="$RemoteSite/32:500"
/interface ipip set ipip1 local-address=$LocalSite remote-address=$RemoteSite

Peer/Policy Update Script – Copy and paste Version

1
2
3
4
5
6
7
8
9
10
11
/system script
add name=dynamic-router-update policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive source="\
    :global LocalSite [:resolve gregsowell-siteA.dyndns.org]\r\
    \n:global RemoteSite [:resolve gregsowell-siteb.dyndns.org]\r\
    \n/ip ipsec policy set 0 sa-dst-address=\$RemoteSite sa-src-address=\$Loca\
    lSite dst-address=\"\$RemoteSite/32:any\" src-address=\"\$LocalSite/32:any\
    \"\r\
    \n/ip ipsec peer set 0 address=\"\$RemoteSite/32:500\"\r\
    \n/interface ipip set ipip1 local-address=\$LocalSite remote-address=\$Rem\
    oteSite"

You can either create a new schedule to run the peer/policy update, or you can just add the script to your existing schedule, which is what I recommend.

1
2
3
4
5
6
/system scheduler
add comment="" disabled=no interval=10m name=dynamic-dns-schedule on-event=\
    "dynamic-dns-script\r\
    \ndynamic-router-update" policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive \
    start-date=jan/01/1970 start-time=00:00:01

Well, there you have it folks. So if you have DHCP at both ends and you are trying to establish a service that requires IP addressing, you can use this script to make it all work. If you feel so inclined, please leave me some feedback if you found this useful.

20 Comments

leave a comment
  1. Wayne Champion / Mar 3 2010

    Hi Greg,

    Thanks for this, it works like a charm.
    If i have multiple sites, would i just modify the Peer/Policy Update Script with the “set” to the different tunnel number? ie 0, 1, 2 etc

  2. Greg / Mar 3 2010

    Wayne,

    You are correct sir. :) Just modify the set number to equal which entry you would like to adjust.

  3. Wayne Champion / Mar 3 2010

    I am impressed thanks again for your good work, keep it up!! :)

  4. Greg / Mar 3 2010

    Wayne, thank you sir and good luck!

  5. Wayne Champion / Mar 11 2010

    After running the solution for a while, it seems that the script to update the peer/policy, doesn’t execute properly, if i manually run it then it works?
    Strange but any ideas?

  6. Greg / Mar 11 2010

    So the IP update script is working, but the settings update is failing. I assume you checked your time and date on the run portion of your script? Possibly you have it set to start January 2010 with a repeat every 5 minutes, but the time on your router accidentally was reset to January 1970?

  7. Wayne Champion / Mar 12 2010

    Hi Greg,
    Yes the script works, but when scheduled it does work. The dates are correct and it also shows me a run count, so the scheduler is working. Could it be that there is a delay in contacting the DNS server? Btw i have several other scripts including the DDNS script running, and they are all working 100%.

    Input is much appreciated

  8. Greg / Mar 12 2010

    Wayne,

    under system -> logging enable script logging. Lets see if anything is being reported. Also, put some informationals in the script every so often so you can see if it is just jamming up on a specific part:
    :log info “$LocalSite”
    :log info “$RemoteSite”

    :log info “got to part1″

    :log info “got to part2″

  9. Wayne Champion / Mar 16 2010

    Ok, Have put that in, but i did add static DNS server on the RB’s and seems to be running better.

    Thanks.

  10. Chris Rees / Mar 16 2010

    Thanks Greg for your great tutorials. I am just getting into Mikrotik Scripting so I can update my WAN IP (Dynamic Private via DHCP) in the Policy settings (on remote sites). What Command or method do you recommend to pull the WAN IP as a global variable to have the script set the Source IP in the Policy.

    Thanks in advance.

  11. Greg / Mar 16 2010

    Chris Rees :

    Thanks Greg for your great tutorials. I am just getting into Mikrotik Scripting so I can update my WAN IP (Dynamic Private via DHCP) in the Policy settings (on remote sites). What Command or method do you recommend to pull the WAN IP as a global variable to have the script set the Source IP in the Policy.

    Thanks in advance.

    Chris,

    Follow the dns update script above. The $currentIP variable is what you are looking for.

  12. Chad Baird / Dec 29 2010

    Greg,

    You don’t know how much you’ve helped me in the past years.. Keep up the good work and have a good new years.

  13. Greg / Dec 30 2010

    @Chad

    Thanks dude. It’s not very often I get a compliment!

  14. william / May 31 2011

    Does the script work on 5.2 ?
    Cuz I had no luck run it on RB750GL-5.2

  15. Greg / May 31 2011

    @William
    I’m not sure sir…I’ve not tested it on V5 code. Try 5.4 as it is the most recent release.

  16. Jason / Jul 15 2011

    Great videos and information by the way. I owe getting OSPF off the ground on my network to you!

  17. Mario / Oct 17 2011

    Hi, Greg!

    I have a question regarding this dns-o-matic thing. I am not sure what this script in the Step 1 is suppose to do. The script for the Site A seems to me like a simple dyndns.org update script. So why to get that dns-o-matic in the game? I am new with all this scripting and dynamic DNS, so your help would be much appreciated…

    btw, I love your videos!

    Mario

  18. Greg / Oct 19 2011

    @Mario
    You are correct, it is just a dyndns update script. I just chose to show that one because it updates nearly any provider.

  19. Mario / Oct 20 2011

    Thanks for clearing it up!

  20. Andre Domingos / Sep 28 2012

    Just a update, I install this script (IPSEC only) in two RG750 v.5.20, I have to modify 3 little things:

    - IPSEC peer (port notation changed):
    /ip ipsec peer
    add address=2.2.2.2/32 port=500 auth-method=pre-shared-key dh-group=modp1024 \
    disabled=no dpd-interval=disable-dpd dpd-maximum-failures=1 \
    enc-algorithm=3des exchange-mode=main generate-policy=no hash-algorithm=\
    md5 lifebytes=0 lifetime=1d nat-traversal=no proposal-check=obey secret=\
    test send-initial-contact=yes

    - IPSEC policy (port notation changed):
    /ip ipsec policy
    add action=encrypt disabled=no dst-address=192.168.2.0/24 dst-port=any \
    ipsec-protocols=esp level=require priority=0 proposal=default protocol=\
    all sa-dst-address=2.2.2.2 sa-src-address=1.1.1.1 src-address=\
    192.168.1.0/24 src-port=any tunnel=yes

    - Schedule (don’t work with two scripts in a row without run):
    /system scheduler
    add comment=”" disabled=no interval=10m name=dynamic-dns-schedule on-event=\
    “/system script run dynamic-dns-script\r\
    \n/system script run dynamic-router-update” policy=\
    ftp,reboot,read,write,policy,test,winbox,password,sniff,sensitive \
    start-date=jan/01/1970 start-time=00:00:01

    Obs. If you have a restritive input filter you need to accept udp port 500 and accept ipsec-esp protocol

    Thanks Greg.

Leave a Comment

*