Over the last few posts I’ve talked about how to set up the basic parts needed to run a small ISP.
In this post I’m going to cover adding a few extra features such as static IP addresses, Bandwidth accounting and Bandwidth limiting/shaping.
Static IP Addresses
We can add a static IP address by adding a field to the users LDAP entry. To do this first we need to add the Freeradius schema to the list of fields that the LDAP server understands. The Freeradius schema files can be found in the /usr/share/doc/freeradius/schemas/ldap/openldap/
and have been gzipped. I unzipped them and copied them to /etc/ldap/schema
then imported it with
$ sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/freeradius.ldif
Now we have the schema imported we can now add the radiusprofile
objectClass to the user along with a radiusFramedIPAddress
entry with the following ldif file.
dn: uid=isp1,ou=users,dc=hardill,dc=me,dc=uk
changetype: modify
add: objectClass
objectClass: radiusprofile
-
add: radiusFramedIPAddress
radiusFramedIPAddress: 192.168.5.2
We then use ldapmodify
to update the isp1 users record
$ ldapmodify -f addIPAddress.ldif -D cn=admin,dc=hardill,dc=me,dc=uk -w password
Now we have the static IP address stored against the user, we have to get the RADIUS server to pass that information back to the PPPoE server after it has authenticated the user. To do this we need to edit the /etc/freeradius/3.0/mods-enabled/ldap
file. Look for the `update` section and add the following
update {
...
reply:Framed-IP-Address := 'radiusFramedIPAddress'
}
Running radtest
will now show Framed-IP-Address
in the response message and when pppoe-server
receives the authentication response it will use this as the IP address for the client end of the connection.
Accounting
Out of the box pppoe-server
will send accounting messages to the RADIUS server at the start and end of the session.
Sat Aug 24 21:35:17 2019
Acct-Session-Id = "5D619F853DBB00"
User-Name = "isp1"
Acct-Status-Type = Start
Service-Type = Framed-User
Framed-Protocol = PPP
Acct-Authentic = RADIUS
NAS-Port-Type = Virtual
Framed-IP-Address = 192.168.5.2
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
Acct-Delay-Time = 0
Event-Timestamp = "Aug 24 2019 21:35:17 BST"
Tmp-String-9 = "ai:"
Acct-Unique-Session-Id = "290b459406a25d454fcfdf3088a2211c"
Timestamp = 1566678917
Sat Aug 24 23:08:53 2019
Acct-Session-Id = "5D619F853DBB00"
User-Name = "isp1"
Acct-Status-Type = Stop
Service-Type = Framed-User
Framed-Protocol = PPP
Acct-Authentic = RADIUS
Acct-Session-Time = 5616
Acct-Output-Octets = 2328
Acct-Input-Octets = 18228
Acct-Output-Packets = 32
Acct-Input-Packets = 297
NAS-Port-Type = Virtual
Acct-Terminate-Cause = User-Request
Framed-IP-Address = 192.168.5.2
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
Acct-Delay-Time = 0
Event-Timestamp = "Aug 24 2019 23:08:53 BST"
Tmp-String-9 = "ai:"
Acct-Unique-Session-Id = "290b459406a25d454fcfdf3088a2211c"
Timestamp = 1566684533
The Stop
message includes the session length (Acct-Session-Time
) in seconds and the number of bytes downloaded (Acct-Output-Octets
) and uploaded (Acct-Input-Octets
).
Historically in the days of dial up that probably would have been sufficient as sessions would probably only last for hours at a time, not weeks/months for a DSL connection. pppoe-server
can be told to send updates at regular intervals, this setting is also controlled by a field in the RADIUS authentication response. While we could add this to each user, it can be added to all users with a simple update to the /etc/freeradius/3.0/sites-enabled/default
file in the post-auth
section.
post-auth {
update reply {
Acct-Interim-Interval = 300
}
...
}
This sets the update interval to 5mins and the log now also contains entries like this.
Wed Aug 28 08:38:56 2019
Acct-Session-Id = "5D62ACB7070100"
User-Name = "isp1"
Acct-Status-Type = Interim-Update
Service-Type = Framed-User
Framed-Protocol = PPP
Acct-Authentic = RADIUS
Acct-Session-Time = 230105
Acct-Output-Octets = 10915239
Acct-Input-Octets = 17625977
Acct-Output-Packets = 25918
Acct-Input-Packets = 31438
NAS-Port-Type = Virtual
Framed-IP-Address = 192.168.5.2
NAS-IP-Address = 127.0.1.1
NAS-Port = 0
Acct-Delay-Time = 0
Event-Timestamp = "Aug 28 2019 08:38:56 BST"
Tmp-String-9 = "ai:"
Acct-Unique-Session-Id = "f36693e4792eafa961a477492ad83f8c"
Timestamp = 1566977936
Having this data written to a log file is useful, but if you want to trigger events based on it (e.g. create a rolling usage graph or restrict speed once a certain allowance has been passed) then something a little more dynamic is useful. Freeradius has a native plugin interface, but it also has plugins that let you write Perl and Python functions that are triggered at particular points. I’m going to use the Python plugin to publish the data to a MQTT broker.
To enable the Python plugin you need to install the freeradius-python
package
$ sudo apt-get install freeradius-python
And then we need to symlink the mods-available/python
to mods-enabled
and then edit the file. First we need to set the path that the plugin will use to file Python modules and files. And then enable the events we want to pass to the module.
python {
python_path = "/etc/freeradius/3.0/mods-config/python:/usr/lib/python2.7:/usr/local/lib/python/2.7/dist-packages"
module = example
mod_instantiate = ${.module}
func_instantiate = instantiate
mod_accounting = ${.module}
func_accounting = accounting
}
The actual code follows, it publishes the number of bytes used in the session to the topic isp/[username]/usage
. Each callback gets pass a tuple containing all the values available.
import radiusd
import paho.mqtt.publish as publish
def instantiate(p):
print "*** instantiate ***"
print p
# return 0 for success or -1 for failure
def accounting(p):
print "*** accounting ***"
radiusd.radlog(radiusd.L_INFO, '*** radlog call in accounting (0) ***')
print
print p
d = dict(p)
if d['Acct-Status-Type'] == 'Interim-Update':
topic = "isp/" + d['User-Name'] + "/usage"
usage = d['Acct-Output-Octets']
print "publishing data to " + topic
publish.single(topic, usage, hostname="hardill.me.uk", retain=True)
print "published"
return radiusd.RLM_MODULE_OK
def detach():
print "*** goodbye from example.py ***"
return radiusd.RLM_MODULE_OK
I was going to talk about traffic shaping next, but that turns out to be real deep magic and I need to spend some more time playing before I have something to share.