Showing posts with label netfilter. Show all posts
Showing posts with label netfilter. Show all posts

Friday, December 02, 2011

I wouldn't be at all surprised to find out I'm the only person in the world running a Counter-Strike Source server on systemd, although I'd be happy to find out that's not the case. Since it was quite painful for me to find useful info on the subject, I figured I'd write up a bit here.

Installing on Linux
First off, Valve's installer won't work if you don't have uncompress on your system. On Fedora, you can make sure with:
yum -y install ncompress

Next, you need to get and run Valve's installer. If you want to keep this stuff in a particular directory, go there, then run:
wget http://storefront.steampowered.com/download/hldsupdatetool.bin
chmod +x hldsupdatetool.bin
./hldsupdatetool.bin
./steam

You will have to type "yes" to the agreement to do the install. Running ./steam should update the platform to the latest version. Next, run this, which could take awhile, to actually install the game server:
./steam -command update -game "Counter-Strike Source" -dir .

After tinkering around, I found that the following command worked well to start the server:
./css/srcds_run -game cstrike -ip 0.0.0.0 -port 27016 -maxplayers 32 -autoupdate +map de_dust

srcds_run is a wrapper that will try to run the right architecture-optimized binary for your system. -ip 0.0.0.0 makes the game listen on all available network interfaces - you probably only really care that it's running on whatever address your system uses to go out to the internet, but the server had trouble figuring which one to use, and this probably won't hurt you. -port 27016 is really optional, but it seems kind of the norm for CS:S servers, at least if you also have a 1.6 server running on 27015, the default. -maxplayers is obvious. -autoupdate will automatically move to any newer versions of the server. +map is different from the other options because it is actually a command that gets run immediately on the server console, not a normal cli option, but you might as well set it here for the sake of automation. FYI, if you have netfilter/iptables running, you could add the following line to /etc/sysconfig/iptables to open up the firewall:
-A INPUT -m state --state NEW -m tcp -p tcp --dport 27016 -j ACCEPT
then run:
service iptables restart

I also had an issue with selinux stopping srcds from from binding to the socket (listening on the network). While I'm sure the "right" thing to do would be to specifically except srcds or even write a policy module for it, I took the easy way out and dropped selinux into Permissive mode. You can do this at runtime by running setenforce 0, and in a persistent way by setting the following in /etc/sysconfig/selinux:
SELINUX=permissive

Now, try running the application, and it should work fine. Yay!

Using systemd
Okay, so first off, I had previously done this with a System V init script, and although it was easier to find examples online regarding how to do that, in the end I'm much happier with my systemd implementation. After searching fruitlessly for help (there's lots of help on running systemctl commands, but not on building unit files), I ended up just using a couple of examples, along with man pages. I started looking at httpd's (apache) unit file, and it turns out it's a little overly-complicated, since httpd understands running as a daemon, so its unit file has Start, Reload and Stop logic. For a simple, non-daemonized application like srcds, I wanted a wrapper that would start it, run it in the background, and just kill it when I send it a stop command. Still, httpd wasn't a bad place to start, and as far as man pages go, systemctl(1)'s SEE ALSO section pointed me to systemd.unit(5), which led me to systemd.service(5) and systemd.exec(5), which were all very helpful as far as understanding directives in the unit file. In the end, here's what I came up with, placed in /lib/systemd/system/cstrike-source.service:

[Unit]
Description=Counter-Strike Source Server
After=syslog.target network.target mysqld.target

[Service]
PIDFile=/var/run/cstrike-source.pid
User=steam
Group=steam
ExecStart=/home/steam/css/srcds_run -game cstrike -ip 0.0.0.0 -port 27016 -maxplayers 32 -autoupdate +map de_dust
KillMode=control-group

[Install]
WantedBy=multi-user.target

The description is arbitrary text, while the "after" section ensures these other services will get started before srcds is run. mysqld was important to me because I use the psychostats (http://www.psychostats.com/) plugin for srcds/sourcemod, with live stats population and in-game stats lookup, which uses mysql as a backend. The PIDFile will be used to track application state. Running as steam prevents some potential security issues. The ExecStart directive gives the actual command to run. Using httpd's unit as an example, I think one could use an EnvironmentFile to configure the command line to run, but I'm keeping everything in this file. KillMode is important, since setting it to "process" would only kill the command systemd ran directly, while the server process under it could just stay running. WantedBy tells systemd what "target" (in SysV land, this is roughly similar to runlevel) to attach this service to. Once this file is created, you can run the following:
chmod +x /lib/systemd/system/cstrike-source.service
systemctl --system daemon-reload
systemctl enable cstrike-source.service
systemctl start cstrike-source.service
systemctl status cstrike-source.service

daemon-reload ensures systemd reads the latest saved version of the unit file. enable turns this service on for starting at boot time. start runs it right now. status tells you it started. At this point, stuff that would've normally printed to your terminal had you run the command yourself will be going to /var/log/messages (assuming a default syslog or syslog-ng configuration). But how will you actually issue commands? One solution is to set an rcon_password in cstrike/cfg/server.cfg, then just run commands over rcon from the game. You could also set up an administration system like mani (http://www.mani-admin-plugin.com/joomla/index.php) or sourcemod (http://www.sourcemod.net/) - in fact, I recommend doing so. However, you can also get rcon access without having a game client running.

Python RCON
A search revealed several libraries and cli tools out there for RCON access, but I ended up going with SourceLib (https://github.com/frostschutz/SourceLib), mostly because I like Python. So far, it works great, and I've made a little command line wrapper around it. Someday I might add command line history, or even autocompletion, but this works for now. :)

#!/usr/bin/env python
import argparse
from SourceLib.SourceRcon import SourceRcon
def main():
parser = argparse.ArgumentParser(
description='Simple RCON client implemented in Python, based on SourceLib')
parser.add_argument('--password', '-p', required=True,
help='password for server')
parser.add_argument('--host', '-H', required=True,
help='hostname/ip address of server')
parser.add_argument('--port', '-P', default=27015, type=int,
help='listening port of server - Default=%(default)s')
parser.add_argument('--timeout', '-t', default=1.0, type=float, metavar='SECONDS',
help='timout to connect to RCON server - default=%(default)s')
args = parser.parse_args()
conn = SourceRcon(host=args.host, port=args.port, password=args.password,
timeout=args.timeout)

while True:
content = raw_input('>> ')
if content == 'exit':
exit(0)
elif content in ['quit']:
choice = raw_input('"quit" will stop the server. "exit" will stop this program. Really quit? (y/N)')
if choice not in ['y', 'Y']:
continue
response = conn.rcon(content)
print response

if __name__ == "__main__":
main()

Save it, chmod +x it, and run it with --help to get an idea how to use it. Once it's running, you can issue RCON commands, set CVARs, etcetera, to your server. Yay!

Wednesday, August 19, 2009

Dansguardian, Tinyproxy (or squid) and IPTables transparent proxy with multiple users on Fedora Linux.

Update: Fedora now has dansguardian in the updates repository, so there's no longer any need for special download instructions.

I've been working on this system for quite awhile now, and drastically improved it as my own understanding of the parts involved has come along. I intend to update this post as I improve the system even more, and I'm certainly open to suggestions from others. The basic sequence of events by the time we're done will be this:
  • Use firewall (iptables) to grab outbound tcp port 80 (http) traffic, except that belonging to tinyproxy, and redirect it (DNAT) to dansguardian.
  • Use firewall (iptables) to grab the packets again, and rewrite the source address (SNAT) based on the user from whom the packets originated.
  • Dansguardian applies content filtering based on who is looking, and logs everybody.
  • Tinyproxy performs the proxying, going out and fetching the actual pages.
I will try to make this as easy as possible for people not already familiar with 'nix, but at the same time some of this information, or at least the way this is put together, will be new to veterans as well. I have been running this system on Fedora, so my instructions are Fedora-specific, but you can probably do most of it on Ubuntu if you just replace my "yum install" commands with "apt-get install", and other Linux systems should be similar as well. If you are a total newbie, probably the biggest hurdles will be editing text files and switching to root access. To gain root access on your system, open a terminal and run "su -", then provide the root password, which you should have specified at install time. If I remember correctly, in Ubuntu you can just run commands preceded by "sudo" to achieve the same result. Of course sudo is available in any system, but in Fedora it requires configuration. To edit a text file, you really should install vi (yum install vim in Fedora) and run vimtutor to get familiar with vi-improved, but in the meantime you can run gedit /path/to/file, which will open the file in the gnu graphical text editor, very similar to notepad or textedit.

I run this system for some friends who have a couple of kids and a young adult living with them. There is just one computer, a desktop, in the house, and it's running Fedora because I can fix that a lot more easily than Windows. Their router/dsl modem hybrid can't run dd-wrt (http://www.dd-wrt.com/dd-wrtv3/index.php), so I'm stuck configuring all of this on the end-user box. I implemented tinyproxy because squid was thrashing the 1.1Ghz processor and 512M of RAM on the system, but they still needed to allow the adult living with them broader access to the web, so I had to figure out a solution. Here it is.

Install Dansguardian
First let's get the basic system up and running. Install Dansguardian: yum -y install dansguardian. The main configuration file for dg is /etc/dansguardian/dansguardian.conf, but we shouldn't need to change anything there yet. By default it's just listening on localhost (which is fine for a transparent proxy), on port 8080. I do recommend editing bannedextensionlist and bannedmimetypelist under /etc/dansguardian/lists/, as the default configuration won't allow much. Any sites you want to explicitly allow for everyone can be added on separate lines in /etc/dansguardian/lists/exceptionsitelist, while bannedsitelist acts as an explicit deny list for everyone. An entry of "facebook.com" would allow anything under facebook.com, including "apps.facebook.com". chkconfig dansguardian on sets it to automatically start during boot, and /etc/init.d/dansguardian start gets it up and running right now.

Install Tinyproxy (or squid)
Well, first I suppose I should explain why I'm mainly focusing on tinyproxy, but I actually use squid on my own laptop. Dansguardian does require a proxy, and either one will do the trick. Squid actually has a lot more features than tinyproxy (wow, wasn't that hard to figure out), like caching (whoo-hoo performance boost) and at least three types of user authentication not (yet) supported by tinyproxy. Check out http://www.squid-cache.org/ and https://www.banu.com/tinyproxy/ for more info. So, why bother with tinyproxy at all? Well, it's a lot more lightweight, so if you aren't going to experience heavy usage, and don't care so much about caching, it can be a lot nicer to your hardware.

Squid actually wouldn't really require any configuration for basic usage other than simply yum -y install squid, service squid start and chkconfig squid on, although it is VERY configurable in /etc/squid/squid.conf. Since this file contains a heck of a lot of comments and empty lines, I like to run:
sed -e '/^#/d' -e '/^$/d' /etc/squid/squid.conf

to view just the actual non-default configuration. In a default install on RHEL 5.1, this cuts the line count from 4,325 to 35. By default, squid listens only on the loopback interface, using port 3128. This is the default port on which dansguardian will look for it, so this works out nicely.

Tinyproxy can be installed with yum -y install tinyproxy, and turned on with service tinyproxy start and chkconfig tinyproxy on. Since tinyproxy listens on port 8888 by default rather than 3128, you either need to change the port specification in /etc/tinyproxy/tinyproxy.conf to 3128 or the proxy port in /etc/dansguardian/dansguardian.conf to 8888. I changed tinyproxy to 3128. Tinyproxy just listens on the loopback by default, which is fine for now. service tinyproxy restart will apply any changes to the config file, or service dansguardian restart if that's what you changed.

At this point, we should be able to put redirects in place, and dansguardian should work with just one set of configuration (i.e. treating everyone the same).

Redirect Traffic
iptables
is just a user interface to load firewall rules into kernel-space memory for use by the nefilter kernel module. Check out http://netfilter.org/ for more information about these tools. For our purposes, we need to catch and redirect all outbound tcp port 80 (http) traffic to our dansguardian server, except for packets belonging to our proxy server, which we want to let through. Although it'd be weird for this not to already be in place, we'll want to ensure that all loopback traffic is allowed, but we may want to also block all traffic on port 3128, except packets belonging to dansguardian, and all traffic on port 8080 that's not destined for dansguardian, to prevent script kiddies from bypassing the filter. If you're a business, you are probably already blocking most ports anyway.

To make sure our loopback stuff will be accepted, run:
iptables -t filter -I INPUT -i lo -j ACCEPT
To redirect our outbound tcp port 80, run:
iptables -t nat -I OUTPUT -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080

To exempt squid from this redirect, run:
iptables -t nat -I OUTPUT -p tcp -m tcp --dport 80 -m owner --uid-owner squid -j ACCEPT
To exempt tinyproxy, run:
iptables -t nat -I OUTPUT -p tcp -m tcp --dport 80 -m owner --uid-owner nobody -j ACCEPT
To block outbound port 3128, run:
iptables -t filter -I OUTPUT -p tcp -m tcp --dport 3128 -j REJECT
To allow dansguardian past this, run:
iptables -t filter -I OUTPUT -p tcp -m tcp --dport 3128 -m owner --uid-owner nobody -j ACCEPT
To block our outbound port 8080, run:
iptables -t filter -I OUTPUT -p tcp -m tcp --dport 8080 -j REJECT
But to allow to dansguardian-destined traffic, run:
iptables -t filter -I OUTPUT -p tcp -m tcp --dport 8080 -d 127.0.0.1 -j ACCEPT
Finally, to save our firewall rules, run:
service iptables save

If you want to act as a gateway, and apply filtering to other systems that route through this one, first add the following line to /etc/sysctl.conf:
net.ipv4.ip_forward = 1
then, run:
sysctl -p
iptables -t nat -I PREROUTING -p tcp -m tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080
iptables -t filter -I FORWARD -j ACCEPT
iptables -t filter -I FORWARD -p tcp -m tcp --dport 8080 -j REJECT
iptables -t filter -I FORWARD -p tcp -m tdp --dport 3128 -j REJECT
service iptables save

All your traffic should now be running through dansguardian! To test this, I recommend using the text-based browser links, which can be installed with yum install links. Try this:
links http://www.google.com
replacing the url appropriately to test. service iptables save writes the file /etc/sysconfig/iptables, which you can edit manually to add/remove/change rules and their order. After editing the file, run service iptables restart to apply the changes. If you want to see a log of packets that match a particular rule, add a duplicate line above the rule, and change everything after "-j" (the target) to "LOG", apply the changes, and run tail -f /var/log/messages.

Enable IP-based Auth
This one was a real kicker for me...If you are using squid, you have plenty of options for determining who's who, and filtering content based on that. However, since tinyproxy doesn't (yet) support authentication, I had to figure out a different method. The documentation I could find suggested either IDENT, with which I was and am unfamiliar, and IP-based Authentication. Obviously IP-based authentication is easy if all you're trying to do is detect from which computer a request came, but I needed it to distinguish between users on a system. I fooled around with IDENT for awhile unsuccessfully before trying to think of a way to use IP-based Authentication.

I tried writing firewall rules to redirect traffic to 127.0.0.2, .3, etc. instead of 127.0.0.1...and dansguardian listened to it, but didn't distinguish...I was about to create a bunch of aliases for eth0 when I remembered dg isn't looking for the IP address of entry into the system, it's looking for the source IP address...enter SNAT! I'd used MASQUERADE before, but never SNAT, so this was a good learning experience for me. Essentially, I added several of the following rules, one for each user, replacing "username" with the user's actual account name, and "X" with a different number for each user. You could support up to 253 separate users this way, I think, although if you used addresses outside of 127.0.0.0/24, which you could, you could support close to 4 billion users:


iptables -t nat -I POSTROUTING -p tcp -m tcp --dport 8080 -m owner --uid-owner username -j SNAT --to 127.0.0.X

Of course once you're done adding your rules, you'll want to service iptables save. Now, nothing has really changed yet from dg's perspective, but we needed this framework in place before we can identify separate users with dg and tp.

Now, we'll set it up in dansguardian. Edit /etc/dansguardian/dansguardian.conf, and uncomment (remove the preceding # from) the line that says "authplugin = '/etc/dansguardian/authplugins/ip.conf'", as well as the one that says "filtergroupslist = '/etc/dansguardian/lists/filtergroupslist'", and change the value of "filtergroups =" to the number of different filtering groups you intend to support. Now edit /etc/dansguardian/lists/filtergroupslist. Follow the instructions of the comments in the file, and pair an IP address to a filtergroup. I ended up using lines like this, with comments to remind me which user is associated with that address, for when I change it sometime:
127.0.0.2 = filter2 #bob

Now, you need to copy the file /etc/dansguardian/dansguardianf1.conf for each of the filter groups you want to use. So, if you set "filtergroups = 5", you could run:
for i in 2 3 4 5; do cp /etc/dansguardian/dansguardianf1.conf /etc/dansguardian/dansguardianf$i.conf; done
Now you can configure each group separately. Some guides that I've read recommend setting group one's "groupmode = 0", which will completely disable access for the group. Since this is the catchall group, anyone not explicitly recognized that placed in another group would be banned. For all groups, you should set "groupname =" to something appropriate and informative. This is especially useful if you want to enable access logging by group, rather than just by user. Well, before I say anything else...now that we have the framework in place, I'll let you look at a site that's much more thorough on the topic of group filtering than I could be: http://contentfilter.futuragts.com/wiki/doku.php?id=filter_groups_configurations

Of course, once all your changes are in place, you'll want to run service dansguardian restart. The nice thing about SNAT is that, until you actually implement IP-based auth in dg, it won't really change anything functionally.

Squid Log Analysis
Coming Soon...

Tinyproxy Log Analysis
Coming Soon...

Dansguardian Log Analysis
Comings Soon...

Installing Webmin and dg-webmin

Coming Soon...