Skip to content

Instantly share code, notes, and snippets.

@angely-dev
Last active October 19, 2022 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angely-dev/e86e319788c31a7207a9ebf924cd6a92 to your computer and use it in GitHub Desktop.
Save angely-dev/e86e319788c31a7207a9ebf924cd6a92 to your computer and use it in GitHub Desktop.
Load balancing between LNS (L2TP), using FreeRADIUS and a custom module (UNLANG).
#
# /etc/freeradius/policy.d/lns_load_balancing
#
# Random load balancing between two LNS.
# Note: the same LNS can be returned twice in a row.
# If it's an issue, feel free to use a non-binary rand interval (e.g., rand:9 instead of rand:2)
# and adapt below conditions (e.g., 0..4 is first LNS, 5..9 is second LNS).
#
lns_load_balancing {
#
# Get random number:
# 0 => keep first LNS only
# 1 => keep second LNS only
#
if ("%{rand:2}" == "0") {
update reply {
&Tunnel-Server-Endpoint:1 !* ANY
&Tunnel-Assignment-ID:1 !* ANY
&Tunnel-Password:1 !* ANY
&Tunnel-Client-Auth-ID:1 !* ANY
&Tunnel-Type:1 !* ANY
&Tunnel-Medium-Type:1 !* ANY
&Tunnel-Preference:1 !* ANY
}
} else {
update reply {
&Tunnel-Server-Endpoint:0 !* ANY
&Tunnel-Assignment-ID:0 !* ANY
&Tunnel-Password:0 !* ANY
&Tunnel-Client-Auth-ID:0 !* ANY
&Tunnel-Type:0 !* ANY
&Tunnel-Medium-Type:0 !* ANY
&Tunnel-Preference:0 !* ANY
}
}
updated
}
@angely-dev
Copy link
Author

FreeRADIUS server sequence:

#
# /etc/freeradius/sites-enabled/my_radius
#
server my_radius {
        #
        # Authentication server
        #
        listen {
                type = auth
                #
        }

        #
        # Accounting server
        #
        listen {
                type = acct
                #
        }

        #
        # Handle an Access-Request
        #
        authorize {
                filter_username
                preprocess
                chap
                suffix
                sql
                lns_load_balancing # custom module
                # some other things…
                expiration
                logintime
                pap
        }
        authenticate {
                #
        }
        post-auth {
                #
        }

        #
        # Handle an Accounting-Request
        #
        preacct {
                #
        }
        accounting {
                #
        }
}

@angely-dev
Copy link
Author

Below an example of the module execution. Tested on FreeRADIUS version 3.0.21.

> SELECT username, attribute, op, value FROM radreply WHERE username = 'my-user-l2tp';

+--------------+--------------------------+----+-------------+
| username     | attribute                | op | value       |
+--------------+--------------------------+----+-------------+
| my-user-l2tp | Framed-Protocol          | := | PPP         |
| my-user-l2tp | Service-Type             | := | Framed-User |
+--------------+--------------------------+----+-------------+
| my-user-l2tp | Tunnel-Server-Endpoint:0 | := | 1.1.1.1     | # LNS1
| my-user-l2tp | Tunnel-Assignment-ID:0   | := | lns1        |
| my-user-l2tp | Tunnel-Password:0        | := | pass1       |
| my-user-l2tp | Tunnel-Client-Auth-ID:0  | := | lac1        |
| my-user-l2tp | Tunnel-Type:0            | := | L2TP        |
| my-user-l2tp | Tunnel-Medium-Type:0     | := | IP          |
| my-user-l2tp | Tunnel-Preference:0      | := | 0           |
+--------------+--------------------------+----+-------------+
| my-user-l2tp | Tunnel-Server-Endpoint:1 | := | 2.2.2.2     | # LNS2
| my-user-l2tp | Tunnel-Assignment-ID:1   | := | lns2        |
| my-user-l2tp | Tunnel-Password:1        | := | pass2       |
| my-user-l2tp | Tunnel-Client-Auth-ID:1  | := | lac2        |
| my-user-l2tp | Tunnel-Type:1            | := | L2TP        |
| my-user-l2tp | Tunnel-Medium-Type:1     | := | IP          |
| my-user-l2tp | Tunnel-Preference:1      | := | 0           |
+--------------+--------------------------+----+-------------+

> SELECT username, attribute, op, value FROM radcheck WHERE username = 'my-user-l2tp';

+--------------+--------------------+----+---------+
| username     | attribute          | op | value   |
+--------------+--------------------+----+---------+
| my-user-l2tp | Cleartext-Password | := | my-pass |
+--------------+--------------------+----+---------+
# First call (LNS1 is returned)
$ radtest my-user-l2tp my-pass localhost 0 testing123
Sent Access-Request Id 16 from 0.0.0.0:51319 to 127.0.0.1:1812 length 82
	User-Name = "my-user-l2tp"
	User-Password = "my-pass"
	NAS-IP-Address = 127.0.1.1
	NAS-Port = 0
	Message-Authenticator = 0x00
	Cleartext-Password = "my-pass"
Received Access-Accept Id 16 from 127.0.0.1:1812 to 127.0.0.1:51319 length 92
	Framed-Protocol = PPP
	Service-Type = Framed-User
	Tunnel-Server-Endpoint:0 = "1.1.1.1"
	Tunnel-Assignment-Id:0 = "lns1"
	Tunnel-Password:0 = "pass1"
	Tunnel-Client-Auth-Id:0 = "lac1"
	Tunnel-Type:0 = L2TP
	Tunnel-Medium-Type:0 = IPv4
	Tunnel-Preference:0 = 0
# Second call (LNS2 is returned)
$ radtest my-user-l2tp my-pass localhost 0 testing123
Sent Access-Request Id 201 from 0.0.0.0:57347 to 127.0.0.1:1812 length 82
	User-Name = "my-user-l2tp"
	User-Password = "my-pass"
	NAS-IP-Address = 127.0.1.1
	NAS-Port = 0
	Message-Authenticator = 0x00
	Cleartext-Password = "my-pass"
Received Access-Accept Id 201 from 127.0.0.1:1812 to 127.0.0.1:57347 length 95
	Framed-Protocol = PPP
	Service-Type = Framed-User
	Tunnel-Server-Endpoint:1 = "2.2.2.2"
	Tunnel-Assignment-Id:1 = "lns2"
	Tunnel-Password:1 = "pass2"
	Tunnel-Client-Auth-Id:1 = "lac2"
	Tunnel-Type:1 = L2TP
	Tunnel-Medium-Type:1 = IPv4
	Tunnel-Preference:1 = 0

FreeRADIUS server log:

(…)
(0)     policy lns_load_balancing {
(0)       if ("%{rand:2}" == "0") {
(0)       EXPAND %{rand:2}
(0)          --> 0
(0)       if ("%{rand:2}" == "0")  -> TRUE
(0)       if ("%{rand:2}" == "0")  {
(0)         update reply {
(0)           &Tunnel-Server-Endpoint:1 !* ANY
(0)           &Tunnel-Assignment-ID:1 !* ANY
(0)           &Tunnel-Password:1 !* ANY
(0)           &Tunnel-Client-Auth-ID:1 !* ANY
(0)           &Tunnel-Type:1 !* ANY
(0)           &Tunnel-Medium-Type:1 !* ANY
(0)           &Tunnel-Preference:1 !* ANY
(0)         } # update reply = noop
(0)       } # if ("%{rand:2}" == "0")  = noop
(0)       ... skipping else: Preceding "if" was taken
(0)       [updated] = updated
(0)     } # policy lns_load_balancing = updated
(…)
(1)     policy lns_load_balancing {
(1)       if ("%{rand:2}" == "0") {
(1)       EXPAND %{rand:2}
(1)          --> 1
(1)       if ("%{rand:2}" == "0")  -> FALSE
(1)       else {
(1)         update reply {
(1)           &Tunnel-Server-Endpoint:0 !* ANY
(1)           &Tunnel-Assignment-ID:0 !* ANY
(1)           &Tunnel-Password:0 !* ANY
(1)           &Tunnel-Client-Auth-ID:0 !* ANY
(1)           &Tunnel-Type:0 !* ANY
(1)           &Tunnel-Medium-Type:0 !* ANY
(1)           &Tunnel-Preference:0 !* ANY
(1)         } # update reply = noop
(1)       } # else = noop
(1)       [updated] = updated
(1)     } # policy lns_load_balancing = updated
(…)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment