OK, let me try to get this done from some short notes - don’t have access to the system my tests are on
…
My script worked by assigning every user to a dedicated uplink (that was required by the customer). This way you will absolutely be sure that the user will go out with the same source ip address every time (masquerading was involved here…). Of course this creates other “load-balancing-fairness” problems, as the users are not distributed among the uplinks using the acutal bandwidth-usage…
Effectively it works similar to the script posted above/in the Wiki: By assigning ip addresses to address lists and routing everyone in one address-list out one uplink.
As I was required to distribute the users so that each user was bound to a dedicated uplink, I was only taking into account the src-addresses. There are two uplinks involved in the example, named uplinkA and uplinkB (genious strikes…).
I put in the following mangle rules:
/ ip firewall mangle
add chain=prerouting src-address-list=use_uplink_a action=mark-routing new-routing-mark=uplink_a passthrough=no comment="set routing mark for uplink A" disabled=no
add chain=prerouting src-address-list=use_uplink_b action=mark-routing new-routing-mark=uplink_b passthrough=no comment="set routing mark for uplink B" disabled=no
add chain=prerouting action=mark-packet new-packet-mark=new_srcadr passthrough=yes comment="" disabled=no
Thus putting a routing-mark for uplinkA on everyone who already is on the address-list “user_uplink_a” and the same for uplinkB. Those routing-marks are later used to force traffic out a specific uplink (not shown in this post). Note that the first two mangle rules do NOT have passthrough activated. So only packets from source addresses NOT already on address-list “use_uplink_a” or “use_uplink_b” reach the third rule which marks packets from “unknown” source addresses with a packet-mark of “new_srcadr”.
Then a firewall filter takes care of putting those unkown (until now) source addresses onto a third address list called “new_user”:
/ ip firewall filter
add chain=forward in-interface=ether1 packet-mark=new_srcadr action=add-src-to-address-list address-list=new_user address-list-timeout=0s comment="" disabled=no
The following script is run via scheduler every few seconds. Its’ purpose is putting newly discovered users (= source addresses) onto one of the address-lists that map users to uplinks:
:local uplinkacount
:local uplinkbcount
:local newipadr
:log debug "script checking for new source addresses"
:foreach i in=[/ip firewall address-list find list=new_user] do={
:set newipaddr [/ip firewall address-list get $i address]
:set uplinkacount [:len [/ip firewall address-list find list=use_uplink_a]]
:set uplinkbcount [:len [/ip firewall address-list find list=use_uplink_b]]
:log debug (" found new source address " . $newipaddr)
:log debug (" " . $uplinkacount . " User auf Uplink A")
:log debug (" " . $uplinkbcount . " User auf Uplink B")
:if ($uplinkacount < $uplinkbcount) do={
:log debug ("about to add " . $newipaddr . " to list a")
/ip firewall address-list remove $i
/ip firewall address-list add address=$newipaddr list=use_uplink_a disabled=no
:log info ("added new source address " . $newipaddr . " to address-list use_uplink_a")
} else={
:log debug ("about to add " . $newipaddr . " to list b")
/ip firewall address-list remove $i
/ip firewall address-list add address=$newipaddr list=use_uplink_b disabled=no
:log info ("added new source address " . $newipaddr . " to address-list use_uplink_b")
}
}
So to sum up, the following way is used to distribute (“load-balance”) users (internal source addresses) to the two uplinks:
If the router detects a source address not already on one of the address-lists that manage the user-to-uplink mapping, it marks packets from that address. A firewall rule then put this source address onto a temporary address-list. The script running every few seconaddds the distributes the users among the two uplinks so that the number of users on each uplink is (more or less) equal.
You could modify the script above to monitor some global variable for each uplink that signals if the uplink is available. If it’s not available, users are not put on the corresponding address-list for that uplink.
Now you can have netwatch (or a own script) check if the uplinks and set the global variable to “not available” when a uplink is down. Then the script would just have to remove all addresses from the address-list for that uplink, and the users would get re-added to the other list.
Some shortcomings, here, too:
- You cannot specify a timeout for an entry on an address-list if you add it via script (opposed to having it automatically added by a firewall rule
), so a user mapped to one uplink will stay there “forever”. This could be easily changed by having a script remove users from address-lists from time to time (to force re-addition to an address-list).
- As the first packets arrive from a “new” user, they will probably not be getting out on the internet, as the source address is not on any “mapping” address-list. So this could mean a few seconds “hick-up” when a new user is coming online. But this depends on how you policy-route your traffic: This is only the case if you don’t let out any traffic from source addresses NOT on the address-lists use_uplink_a/use_uplink_b.
After all, these might be just some confusing bits and pieces, but until I get access to the test system again I thought I put them here for you guys to read over and perhaps already someone using the idea for something…
Comments welcome…
Best regards,
Christian Meis