Add it to your bundles in vim and, for extra magic, just map some key to :AT in your vimrc. I have added this one:
map <F4> :AT<CR>
I don't know how I lived without this for such a long time.
Add it to your bundles in vim and, for extra magic, just map some key to :AT in your vimrc. I have added this one:
map <F4> :AT<CR>
I don't know how I lived without this for such a long time.
Now that we have a basic gateway we can do crazy stuff, like installing a proxy. You may want to manually configure a proxy for each client, but you can also choose to install a transparent proxy for all your users. This can be done with squid, let's see how.
Start by installing squid on your gateway. You can choose a different machine, but you'll have to do some magic with iptales. It's easier to just use the same machine.
Once squid is installed head to /etc/squid/ to vim squid.conf. Yes, it's very scary to see such a long config file, but it's mostly just comments. Luckily squid has reasonable defaults, so you can just ignore most of this file. Just to test if your squid installation was successful, before changing anything, you can tail -f /var/log/squid/access.log and set your browser's proxy to your gateway's IP, port 3128 (squid's default port). If everything works you should be able to browse and also see the access logs scrolling by.
If you are getting a 'denied' page on every request, you may have to configure squid to allow http access. Search for the 'http_access deny all' and comment it. You may also have to search for the local networks definitions and set it up correctly (something like 'acl localnet src 192.168.0.0/24').
Once you have verified that your proxy is working, you can configure it to run on transparent mode. Search for the http_port directive, and change it to something like 'http_port 8213 transparent' (noticed I changed the default port). It is also a good practice to specify IP and port, so squid can bind only to the local interface (you are probably not interested in serving as a proxy for the outside world, unless you plan to run a reverse proxy).
Changing squid to run on transparent mode is not enough, though. You will also need to tell your router to redirect every incoming packet from port 80 to squid. Assuming your LAN is on the 192.168.10.0/24 address and squid is listenning on port 1234, you can use this magic command to setup your iptables rule:
iptables -t nat -A PREROUTING -s 192.168.10.0/24 -p tcp --dport 80 -j DNAT --to :1234
If this doesn't work for you, or you want a more detailed explanation, you can check my post about this iptables rule.
Everything ready, you should be able to unconfigure the proxy from your browser and start using squid right away, no configuration needed. tail -f /var/log/squid/access.log for hours of (thought-policing) fun.
Now that you have a gateway and a transparent proxy, it's time to install a content filter too. It's not hard, just go to your squid's config file and search for the acl section. Over there, add the following two lines:
acl blocksites url_regex "/home/router/blocked_sites.acl"
http_access deny blocksites
This will include the blocked_sites.acl file and deny access to every URL on it. There are many blacklist services out there, from which you can download a nice filter to suit your needs.
Of course, you probably don't want to restart squid each time a new site is added to your blocklist. For this you can use "squid -k reconfigure" to make squid reload its configuration.
Some random tips for squid:
To decipher weird c declarations go to http://cdecl.org/ and type your type. It works for most cases... good luck trying to figure out templates, though, for template metaprogramming you are on your own.
In any LAN you'll probably want to expose some services to the outer world, be it for a bittorrent connection or because you have internal servers you need to access from outside your internal LAN. To do this, you'll have to tell your router to forward some external port to an internal one, like this:
iptables -t nat -A PREROUTING -i eth0 -p tcp
--dport PORT -j DNAT --to INTERNAL_IP:INTERNAL_PORT
# This rule may not be needed, depending on other chain confings
iptables -A INPUT -i eth0 -p tcp -m state --state NEW
--dport PORT -j DNAT --to INTERNAL_IP:INTERNAL_PORT
This is enough to expose a private server to the world, but it will not be very useful when your dynamic IP changes, so you'll need to set INTERNAL_IP to be a static IP.
Of course, this commands are little less than black magic. iptables are rather complex and quite difficult to master, but as a short description we can say they are a way of applying a set of rules to incoming network packets. In iptables you have different tables of rules (in this case we use -t[able] nat) and specify that we want our rule to be applied in the PREROUTING phase. -i specifies that this rule should be applied only to packets incoming from eth0, and --dport means this rule applies only to packets incoming from a certain port. Of course, if you are going to specify a port then you need to specify the protocol (in this case, tcp).
Now we have replicated in our setup almost all the functionalities a small COTS router has. Next time we'll see how to improve that by adding a proxy.
Luckily you can easily restore your state if you just write all the gdb commands you need into a file, then start gdb with "--command=state.gdb". Magic! All your breakpoints are there.
Alternatively, an even better solution: just don't exit gdb after recompiling, simply "kill" your currently under-debug process (ie type "kill" inside gdb, do not kill gdb itself!) and gdb will be smart enough to reload your binary if it changed.
Since I want to keep everything together for easy administration, I will move the configuration files for DHCP to /home/router/dhcp. Changing the dhcpd.conf file itself is easy, just move the subnets declarations and add this line:
include "/home/router/dhcp/subnets.conf";
include "/home/router/dhcp/static_hosts.conf";
Like we did before with bind, we need to configure apparmor. vim /etc/apparmor.d/usr.sbin.dhcpd and add this two lines:
/home/router/dhcp/ rw,
/home/router/dhcp/** rw,
Restart apparmor service, then restart dhcpd. Everything should work just fine.
Remember the static_hosts file we created before? We can use that to define a static IP. Add the following to set a static IP host:
host HostName {
hardware ethernet 00:00:00:00:00:00
fixed-address 192.168.10.50;
}
After that, just restart the DHCP service and renew your client's IP. Done, it's static now!
Wait a minute: how do you find the MAC for your host? I'm to lazy to copy and type it, so I did the following:
cd /home/router/dhcp
ln -s /var/lib/dhcp leases
Then you can check the hardware address in the leases/dhcpd.leases file. I created a symlink to keep this directory at hand, since it gives you a status of the current leases.
Luckily gcc seems to have an option to verify that your functions do not use an unbounded amount of stack space: just compile with the option "-fstack-usage" and a file .su will be generated with stack information for each function.
You probably want to see only static or bounded stack usages; an unbounded stack usage might be a sign that you should be storing that object on the stack instead.
Edit /etc/dhcp/dhcpd.conf, set the domain-name and the domain-name-servers, like this:
option domain-name "lan";
option domain-name-servers 192.168.10.1 192.168.0.1;
default-lease-time 86400;
max-lease-time 172800;
authoritative;
I'm not sure if the first line is needed. The other two will set the DNS servers for your clients. Also, increasing your lease time is recommended, I used one day for default leases. I set this DHCP server as the authoritative server. If this is your router, that's probably what you want.
Now we need to define the network topology:
# This is the WAN network, and we won't provide a service here
subnet 192.168.0.0 netmask 255.255.255.0 {
}
# Define the service we provide for the LAN
subnet 192.168.10.1 netmask 255.255.255.0 {
range 192.168.10.100 192.168.10.200;
option routers 192.168.10.1;
}
Now we need to restart ISC:
sudo /./etc/init.d/isc-dhcp-server restart
And now we need to check if everything worked in the client. It's easy this time, we just ask for an IP:
sudo dhclient
ifconfig
If everything went fine, we should now have an IP in the 100-200 range, as well as the DNS server in /etc/resolv.conf. We have now setup a very basic router and should be able to server several clients for basic browsing capabilities.
Next time we'll see how to tidy up everything, for easier administration.
I can only imagine how incredibly ugly their lexer must be to say it's not a fixable problem.
After installing bind9 we should get a new file in /etc/apparmor.d/usr.sbin.named. Add the following lines at the bottom:
/home/router/named/** rw,
/home/router/named/ rw,
And restart apparmor service:
/./etc/init.d/apparmor restart
Since we were modifying apparmor to allow a non-standard bind installation, now restart bind too. This time it will start without any errors, and you should be able to tail -f /home/router/named/dns.log to see the DNS queries on real time. If it doesn't, check that /home/router/named is writable to the bind user (I did a chgrp -R bind named).
template <typename T> struct get_real_type { typedef T type; };
template <typename T> struct get_real_type<T*> { typedef T type; };
template <typename T> struct get_real_type<T&> { typedef T type; };
template <class T>
int foo() {
return get_real_type<T>::type::N;
}
struct Bar {
static const int N=24;
};
#include <iostream>
using namespace std;
int main() {
cout << foo<Bar*>() << endl;
cout << foo<Bar&>() << endl;
cout << foo<Bar>() << endl;
}
Incidentally, this is also the basis for the implementation of std::remove_reference. Actually you'd be better of using std::remove_reference, for your own sanity.
ping google.com
You should get a message saying the host is unknown. Can you guess why? Right, there's no DNS.
DNS will be necessary to resolve domains to IPs. bind9 is the default option for Debian based servers (are there others? no idea).
sudo apt-get install bind9
This will get your DNS server up and running, but you will still need to add this server manually to your client (again, because there's no DHCP running):
sudo echo "nameserver 192.168.10.1" > /etc/resolv.conf
And now:
ping google.com
Magic again, it (may) work. If it doesn't, you may need to open /etc/bind/named.conf and setup your router (192.168.0.1) as a forwarder, then restart the bind server.
Of course this is rather boring. If you are going to install a DNS you might as well create a custom TLD for your LAN.
So far on the series about how to install a Linux based router, we set up a Linux router with NAT and a basic DNS. Now we'll setup a custom TLD, so you can have custom domains for your LAN. For example, if you want your router to have a nice user friendly name, instead of just an IP.
Let's start by adding a local zone to /etc/bind/named.conf.local, for a domain we'll call "lan":
zone "lan" {
type master;
file "/home/router/named/lan.db";
};
Now we need to add a reverse zone. Note how the name is the IP reversed:
zone "10.168.192.in-addr.arpa" {
type master;
file "/home/router/named/rev.10.168.192.in-addr.arpa";
};
We still need to create both files (lan.db and rev.10.168.192.in-addr.arpa), but will do that later. Lets setup a place to log all the DNS queries (optional):
logging {
channel query.log {
file "/home/router/named/dns.log";
severity debug 3;
print-time yes;
};
category queries { query.log; };
};
For the log entry I have chosen /home/router/named as the log directory, just because for this project I'm keeping everything together (config and logs) so it's easy for people not used to administer a Linux box, but of course this means that apparmor must be configured to allow reads and writes for bind in this directory. We'll get to that in a second, first let's create the needed zone files for our new TLD.
Remember our two zone files? I put them on /home/router/named, but usually they are on /etc/bind. Again, I did this so I can have all the config files together. These are my two files:
For lan.db
lan. IN SOA ns1.lan. admin.lan. (
2006081401
28800
3600
604800
38400
)
lan. IN NS ns1.lan.
wiki IN A 192.168.0.66
ns1 IN A 192.168.0.1
router IN A 192.168.0.1
For rev.10.168.192.in-addr.arpa
@ IN SOA ns1.lan. admin.example.com. (
2006081401;
28800;
604800;
604800;
86400
)
IN NS ns1.lan.
1 IN PTR lan
Most of these lines are black magic, and since an explanation of both DNS and Bind is out of scope (feel free to read the RFC if you need more info) let's just say you can add new DNS entries by adding lines like this:
NICE_NAME IN A REAL_IP
This will make bind translate NICE_NAME.lan to REAL_IP. Of course, this will depend on the TLD you defined. Now restart bind to get a crapton of errors. It will complain about not being able to load a master file in /home/router/named. Remember that apparmor thing I mentioned?
While you won't escape the need to learn some assembly to get any meaningful information out of gcc's assembly listing, there are some tips which might make your life much easier:
c++filt is part of the build essentials package, and will turn mangled names into proper C++ names. You won't need to remember that _Znwm is the mangled version of "operator new", just run "g++ -E foo.cpp -o /dev/stdout | c++filt" and you'll get an assembly with unmangled names.
Some people have the ability to read assembly and automatically understand how the data flows between registers and variables very quickly. For the mere mortals like us, gcc has a very helpful flag called "-fverbose-asm" which will add a comment to each line where a variable is referenced. This will let you keep track of the data flow inside a function.
As far as I know, gcc has no option to write to stdout; just use "-o /dev/stdout" to let it write to a fake file which Linux will helpfully create for you, then you can pipe the hell out of gcc's output.
We'll do this by setting up NAT with iptables. We'll also have to configure the OS to forward connections from one network card to the other:
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables --table nat --append POSTROUTING --out-interface eth0 -j MASQUERADE
# Add a line like this for each eth* LAN
iptables --append FORWARD --in-interface eth1 -j ACCEPT
We will also need to setup the IP for eth0, since there won't be a DHCP server (we ARE the server!). Open /etc/network/interfaces and add something like this:
# Configure the WAN port to get IP via DHCP
auto eth0
iface eth0 inet dhcp
# Configure the LAN port
auto eth1
iface eth1 inet static
address 192.168.10.1 # (Or whatever IP you want)
netmask 255.255.255.0 # Netmasks explanations not included
Once that's checked, restart networking services like this:
sudo /./etc/init.d/networking restart
Everything ready, now just plug your PC to the new router and test it. Remember to manually set your IP in the same network range as your router, since there's no DHCP at the moment. This may be useful to debug a problem.
In your client PC, set your IP address:
ifconfig eth0 192.168.10.10
Test if you IP is set:
ping 192.168.10.10
If you get a reply, your new IP is OK, if not there's a problem with your client. Second step, see if you can reach the router:
ping 192.168.10.1
Note that you may need to renew everything (i.e. restart networking and manually assign your IP) after you connect the cable.
Again, if you get a reply then you have connectivity with the router. So far we haven't tested the iptables rules nor the forwarding, so any issue at this point should be of IP configuration. If everything went well, it's time to test the NAT rules and the forwarding.
ping 192.168.1.1
That should give you an error. Of course, since there's no DHCP there's no route set. Let's manually set a route in the client:
sudo route add default gateway 192.168.10.1
Then again:
ping 192.168.0.1
Magic! It works! If it doesn't, you have a problem either in the NAT configuration or the IP Forwarding of the router. You can check this with wireshark, if the pings reach the server but they never get a reply back then it's the NAT, i.e. it can forward the IP packages on eth1 to eth0 but the router has no NAT, and it doesn't know how to route the answer back. If the pings never even reach eth0, then you have an ip forwarding problem.
In order to have the forwarding rules persisting after a reboot, we need first to change /etc/sysctl.conf to allow IP forwarding. It's just a mater of uncommenting this line:
net.ipv4.ip_forward = 1
We will also have a lot of iptables rules we need to setup during boot time. I have created a script at /home/router/set_forwarding.sh, which I also linked into /etc/init.d/rc.local so it's run whenever the system boots.
Next time we'll move on to something more complex: installing a DNS server and using domains instead of IPs.
Clearly that's too easy for C. We need more complexity: we need to make the expansion rules interact with the argument substitution process and the preprocessor operators too!
How exactly? The whole process is specified by a very tiny paragraph, 16.3.1, on the standard, which despite being tiny contains a lot of information. Actually, it contains all the expansion and precedence rules for the preprocessor. And it's more or less like this:
Let's take this example:
#define str(...) #__VA_ARGS__
#define foo(a, b) foo a bar str(b)
#define bar foo bar 1
foo(bar, (1, 2, 3))
How can we expand this macro call? Like this:
expand{ foo(bar) }
Match foo with definition of macro: foo(a)
Bind a to bar
Macro expand argument a -> expand{ bar }
bar takes no arguments, no binding is done
Apply rule bar -> foo bar 1
Scan the result for new expanions
foo was already expanded, no further expansion
Bind b to (1, 2, 3)
Macro expand argument b -> nothing to expand
Replace macro expanded arguments in body definition:
-> foo foo bar 1 bar str((1, 2, 3))
Rescan the body for further expansion:
foo: Already expanded on current scope
foo: Already expanded on current scope
bar: Already expanded (The compiler will have too keep a map of expanded macros for each identifier in a definition!)
bar: Needs expansion
Apply rule bar -> foo bar 1
Rescan for further expansion
foo: Already expanded on parent scope
bar: Already expanded on current scope
str((1, 2, 3)): Expand macro call
Bind (1, 2, 3) to __VA_ARGS__
Analyze (1, 2, 3) for further expansion
Apply operator '#' to (1, 2, 3) -> "(1, 2, 3)"
Replace #__VA_ARGS__
Replace the result of str((1,2,3)) -> "(1, 2, 3)"
Replace the original call "foo(bar, (1, 2, 3))" for the result
-> foo foo bar 1 foo bar 1 "(1, 2, 3)"
This last example should be a good representative of the complexities involved in a macro expansion; hopefully now you know more than you ever wanted to know about macros.
I'm going to write about how can you setup a regular Linux distro to be your border router/gateway for your LAN, but for easy of use I'll base my examples on Ubuntu.
As expected, if we are going to replace a device, say, a router, we need to replace it with something that can provide the same functionality. In this case, we have chosen a Linux server, so we need to figure out which services are provided by the router and then emulate them someway:
You will also need an OS. I have chosen Ubuntu because it's very simple to install, and has all the software we need available in the repositories, but you can use any other distribution if it suits your needs.
Also, throughout this guide I will assume a setup like this:
Let's start with a simple example:
#define foo bar 1
#define bar foo 2
foo
Luckily the preprocessor is smart enough not to trip up on this simple piece of code. When expanding foo on line three it will do something like this:
#define foo bar
#define bar foo
foo
// Applies foo -> bar 1
bar 1
// Applies bar -> foo 2
foo 2 1
// Scans foo again... but doesn't expand it
The second time the preprocessor scans foo it won't expand it: it "knows" foo was already expanded, so it won't do it again. But how does it know that foo was already expanded? Let's try something a bit more complicated:
#define foo bar a baz b
#define bar foo 1
#define baz bar 2
foo
And then let's see how foo is expanded, step by step:
#define foo foo a bar b baz c
#define bar foo 1
#define baz bar 2
foo
First the rule "foo -> foo a bar b baz c" will be applied and the results rescanned: let's call this scope 1. We'll end up with:
foo a bar b baz c
Now the results of this expansion will be scanned, in a new scope. Let's call it scope 2. The first token the preporcessor will see is "foo", which was already expanded on scope 1: it will be ignored and it will continue to the next expandable token, "bar", and it will expand it like this:
foo a foo 1 b bar 2 c
On the scope that baz's expansion creates (scope 4), the parent's scope expansion rules are "inherited", so for scope 4 "foo" was already expanded but "bar" was not, because bar's expansion happened on scope 3 and scope 3 is not scope's 4 parent. Not following me? Try following this diagram:
foo -> foo a bar b baz c
foo -> already expanded, ignore
a -> not a macro, ignore
bar -> expand to "foo 1"
foo -> expanded at parent scope, ignore
1 -> not a macro, ignore
b -> not a macro, ignore
baz -> expand to "bar 2"
bar -> expand to "foo 1"
foo -> already expanded at parent scope, ignore
1 -> not a macro, ignore
2 -> not a macro, ignore
c -> not a macro, ignore
Hopefully the preprocessor expansion rules should be a bit more clear now: each expansion creates a scope, each scope inherits from parent's scopes whether a rule was applied or not and if it was then said rule is ignored in the current scope.
Of course these rules get more complicated when dealing with token pasting and stringifying operators, because each phase (stringifying, token pasting, rescanning and expansion) will happen in a specific order. Things get even more complicated when you realize (by reading the standard) that said order is not the same when you deal with argument replacement.
Then again, it's probably a good idea if your macros don't rely on the recursive expansion rules of the preprocessor.
Just add this magic spell to your .vimrc; next time you open a file in a project with a tags file generated just press ctrl-enter with your cursor over the definition you wish to find:
map <C-CR> :tab split<CR>:exec("tag ".expand("<cword>"))<CR>
#if 0
# if 0
# else
# elif true
# endif
#endif
Clearly the inner if is wrong because the else clause comes before the elseif, however you might think it doesn't matter because it's surrounded by an #if 0. Surprise: it does matter, that's not valid preprocessor input. Even if the outer #if is not "taken", whatever preprocessing directives are inside it should still be valid (though anything that's not a preprocessing directive will indeed be ignored).
Even though at first it might seem weird for things inside an #if 0 to be important, it makes sense if you think that should an internal #if not respect the proper structure then the preprocessor wouldn't know when to end the first #if 0. Then again, if you find any real-world utility to this bit of preprocessor implementation trivia, you are doing something horribly wrong!