├── .gitignore ├── AUTHORS ├── COPYING ├── ChangeLog ├── HOWTO ├── INSTALL ├── LICENSE.md ├── Makefile.am ├── Makefile.win ├── NEWS ├── README ├── TODO ├── X64 └── Makefile ├── X86 └── Makefile ├── acl.c ├── acl.h ├── bootstrap.sh ├── client.c ├── client.h ├── config.h.in ├── config.h.win ├── configure.ac ├── conn.c ├── conn.h ├── diag.c ├── diag.h ├── dlist.c ├── dlist.h ├── dsr.c ├── dsr.h ├── epoll.c ├── event.c ├── event.h ├── idlers.c ├── idlers.h ├── kqueue.c ├── memory.c ├── memory.h ├── mergelogs.1 ├── mergelogs.c ├── netconv.c ├── netconv.h ├── pen-ocsp.sh ├── pen.1 ├── pen.c ├── pen.h ├── pen.spec ├── pen_epoll.h ├── pen_kqueue.h ├── pen_poll.h ├── pen_select.h ├── penctl.1 ├── penctl.c ├── penctl.cgi ├── penlog.1 ├── penlog.c ├── penlogd.1 ├── penlogd.c ├── penstats ├── poll.c ├── select.c ├── server.c ├── server.h ├── settings.c ├── settings.h ├── siag.pem ├── ssl.c ├── ssl.h ├── testsuite.sh ├── windows.c └── windows.h /.gitignore: -------------------------------------------------------------------------------- 1 | # autoconf/automake 2 | Makefile.in 3 | /aclocal.m4 4 | /autom4te.cache/ 5 | /compile 6 | /configure 7 | /depcomp 8 | /install-sh 9 | /ltmain.sh 10 | /missing 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ulric Eriksson 2 | UDP support contributed by the Zen project. 3 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UlricE/pen/2c09b7a82122836ee26e6c7e6a141d0b44b724a6/ChangeLog -------------------------------------------------------------------------------- /HOWTO: -------------------------------------------------------------------------------- 1 | HTTP 2 | ---- 3 | 4 | pen -l pen.log -p pen.pid lbhost:80 host1:80 host2:80 5 | 6 | If pen is running on one of the web servers, it might seem like 7 | a good idea to simply use an alternative port for the web server 8 | process, reusing the IP address. Unfortunately, that doesn't work 9 | very well. Look at this (simplified) example: 10 | 11 | sh-2.05# pen lbhost:80 lbhost:8080 12 | sh-2.05# telnet lbhost 80 13 | Trying 127.0.0.1... 14 | Connected to lbhost. 15 | Escape character is '^]'. 16 | GET /bb 17 | 18 | 19 | 301 Moved Permanently 20 | 21 |

Moved Permanently

22 | The document has moved here.

23 |


24 |
Apache/1.3.14 Server at lbhost Port 8080
25 | 26 | Connection closed by foreign host. 27 | 28 | This will cause the client to attempt to contact the web server 29 | directly, which may not be possible depending on firewall configuration 30 | and is certainly not desirable since it defeats any load balancing 31 | attempts from pen. 32 | 33 | 34 | The solution is to bind two addresses to the server running pen, and 35 | use one address for pen and the other for the web server. Like this: 36 | 37 | pen address1:80 address2:80 server2:80 38 | 39 | Here, address1 and address2 refer to the same server, while server2 40 | refers to another server. 41 | 42 | 43 | The programs penlog and penlogd are used to combine the web server 44 | logs into a single file which can be used to calculate statistics. 45 | Penlog runs on each of the web servers. It reads log entries from 46 | stdin and sends them over the network to the host running penlogd. 47 | For Apache, this is accomplished by adding a line similar to this 48 | to httpd.conf: 49 | 50 | CustomLog "|/usr/local/bin/penlog loghost 10000" common 51 | 52 | For other web servers, the procedure is different. If the server 53 | cannot write its logs to a pipe, this kludge may actually work: 54 | 55 | tail -f /path/to/logfile | penlogd loghost 10000 56 | 57 | The command line to pen must also be altered to indicate that the 58 | logs should go to the penlogd server rather than a file. This is 59 | accomplished using the 60 | 61 | -l loghost:10000 62 | 63 | option. 64 | 65 | The log file pen.log is used to combine the web server logs into a 66 | single file which can be used to calculate statistics. Example: 67 | 68 | mergelogs -p pen.log \ 69 | 10.0.0.1:access_log.host1 10.0.0.2:access_log.host2 \ 70 | > access_log 71 | 72 | The program mergelogs is distributed with pen. Use matching versions 73 | of pen and mergelogs to ensure that the log file format is compatible. 74 | 10.0.0.1 and 10.0.0.2 are the IP addresses of host1 and host2. The 75 | log files access_log.host1 and access_log.host2 are Apache access log 76 | files in combined or common format. The resulting access_log is in 77 | the same format as the input files. 78 | 79 | If the log file will be used to calculate visitor statistics, you 80 | probably want host names rather than IP addresses. This can be 81 | accomplished by forcing the web server to do hostname lookups on 82 | the clients. This harms performance since the lookups are slow. 83 | 84 | A better solution is to use a separate program to process the log 85 | file. One such program is webresolve, which is usually run from the 86 | splitwr script to perform many lookups in parallel. Example: 87 | 88 | splitwr access_log > access_log.resolved 89 | 90 | Webresolve is available from http://siag.nu/webresolve/. 91 | 92 | 93 | HTTPS 94 | ----- 95 | 96 | pen -l pen.log -p pen.pid lbhost:443 host1:443 host2:443 97 | 98 | Otherwise identical to http (for our purposes), https uses port 443. 99 | 100 | Using pen for the ssl encapsulation: 101 | 102 | pen -l pen.log -p pen.pid -E mycert.pem lbhost:443 host1:80 host2:80 103 | 104 | 105 | HTTP + HTTPS 106 | ------------ 107 | 108 | pen -l pen80.log -p pen80.pid -h lbhost:80 host1:80 host2:80 109 | pen -l pen443.log -p pen443.pid -h lbhost:443 host1:443 host2:443 110 | 111 | If we are using http and https in parallel for a single site and 112 | need to make sure that requests go to the same server for both protocols, 113 | we use the -h (hash) option. Note that it is still possible for a 114 | client to end up with a split connection if e.g. http is down on 115 | host1 and https is down on host2. 116 | 117 | The server selection can be made completely deterministic by adding 118 | the -s option. Pen will then stubbornly refuse to fail over to another 119 | server if the first choice is unavailable. Obviously, this means the 120 | site will fail for some clients if either http or https is down on 121 | any server, which is unsatisfactory. 122 | 123 | A better solution is to use a single protocol for all communication, 124 | or design the application such that split connections do not matter. 125 | For example, serve static content such as images over http. 126 | 127 | 128 | SMTP 129 | ---- 130 | 131 | pen -l pen.log -p pen.pid -r lbhost:25 host1:25 host2:25 132 | 133 | This is straightforward enough, with the added twist that all 134 | connections to host1 and host2 appear to come from lbhost. It is 135 | therefore important that care be taken so that the setup can't be 136 | used as an open relay for spam. 137 | 138 | An example use for load balancing with smtp is for large sites with 139 | a high volume of outgoing mail. 140 | 141 | 142 | FTP 143 | --- 144 | 145 | pen -l pen.log -p pen.pid lbhost:21 host1:21 host2:21 146 | 147 | The ftp protocol has quirks that makes load balancing more difficult 148 | than many other protocols. Details in RFC959, but here is an example 149 | from a pen debug session: 150 | 151 | copy_up(0) 152 | 23: PORT 127,0,0,1,12,212 153 | 154 | copy_down(0) 155 | 30: 200 PORT command successful. 156 | 157 | copy_up(0) 158 | 18: RETR loadlin.exe 159 | 160 | copy_down(0) 161 | 72: 150 Opening BINARY mode data connection for loadlin.exe (32177 bytes). 162 | 163 | copy_down(0) 164 | 24: 226 Transfer complete. 165 | 166 | Notice how the last two entries claim that the transfer is first 167 | "opening", and then "complete" with no intermediary step. 168 | 169 | In other words, the initial connection is just a command telnet stream. 170 | For the data transfer, the client opens a port of its own and the server 171 | connects directly there, bypassing the load balancer. 172 | 173 | There isn't necessarily anything particularly wrong about that, 174 | except that the server will connect from an address that the client 175 | doesn't expect, and it will have to connect *to* an address that is 176 | different from the peer in the command stream. In addition, it may not 177 | work if the server is behind a firewall and it most certainly won't 178 | work if pen is acting as the firewall. 179 | 180 | A solution would be to intercept the PORT command, open a connection 181 | there, listen to a socket of our own and send a new PORT command to 182 | the server instead of the one the client sent. We would also need to 183 | track the command stream during the data transfer, and check for things 184 | like ABOR and STAT. And we'd have to implements all kinds of 185 | workarounds for buggy clients and servers. 186 | 187 | Messy indeed, and pen doesn't even try. Browse through an ftp client 188 | some time; it's entertaining to see what programmers think of the 189 | protocol. 190 | 191 | 192 | Here is a recipe for ftp load balancing that is portable, works and is 193 | easy to implement. 194 | 195 | First a snippet from /etc/hosts. The IP addresses are supposed to be 196 | public addresses. It is possible to play tricks with NAT to remove 197 | that requirement, but that is beyond the scope of this document. 198 | 199 | 123.123.123.1 lbhost 200 | 123.123.123.2 host1 201 | 123.123.123.3 host2 202 | 203 | ftp servers are running on host1 and host2. Use this command to 204 | start pen on lbhost: 205 | 206 | pen -l pen.log -p pen.pid lbhost:21 host1:21 host2:21 207 | 208 | Incoming connections from clients are distributed to host1 and host2, 209 | transparently to the user. Outgoing connections from host1 and host2 210 | bypass lbhost. If there is a firewall between the servers and the 211 | Internet, it must permit incoming traffic to lbhost on port 21 and 212 | outgoing traffic from host1 and host2. 213 | 214 | This can be prevented from working if the client is behind a firewall 215 | that does its own stateful session tracking (for example, setting up 216 | temporary rules that let the server access the client on the port 217 | given in the PORT command), but there's little or nothing we can do 218 | about that. Such schemes break anyway if the server has multiple IP 219 | addresses, load balancing or not. 220 | 221 | Picky servers that refuse to set up data streams that bypass the load 222 | balancer won't work either, but then we at least have the option to 223 | replace the server. The stock Solaris ftpd is fine; wu-ftpd 2.6.0 224 | is not. 225 | 226 | 227 | POP3 228 | ---- 229 | 230 | The pop3 service normally runs from inetd. In that case, it is not 231 | possible to use multiple IP addresses to allow pen and the pop3 232 | server to run on the same host. However, there's nothing to stop us 233 | from using multiple ports. Add this to /etc/services: 234 | 235 | pop3-pen 1110/tcp 236 | 237 | And change /etc/inetd.conf like this: 238 | 239 | # pop3 stream tcp nowait root /usr/sbin/tcpd in.pop3d 240 | pop3-pen stream tcp nowait root /usr/sbin/tcpd in.pop3d 241 | 242 | Note how the old entry for pop3 is commented out. Restart inetd and run 243 | 244 | pen -p pen.pid pop3 localhost:1110 otherhost:pop3 245 | 246 | Pen will listen to the pop3 port and distribute incoming requests 247 | to the local pop3 server running on port 1110 and to the pop3 server 248 | running on "otherhost" on the standard port. 249 | 250 | 251 | LDAP 252 | ---- 253 | 254 | (This was sent as a reply to a question is pen could handle load 255 | balancing and failover for a group of ldap servers.) 256 | 257 | As far as I know, LDAP is a simple tcp based protocol. 258 | The following experiment on my laptop was successful: 259 | 260 | 1. Install OpenLDAP 261 | 262 | 2. Run slapd like this: 263 | 264 | /usr/local/libexec/slapd -d 255 265 | 266 | 3. Run pen like this: 267 | 268 | pen -d -d -f 3890 localhost:389 269 | 270 | 4. Run ldapsearch like this: 271 | 272 | ldapsearch -H ldap://localhost:3890 \ 273 | -x -b '' -s base '(objectclass=*)' namingContexts 274 | 275 | 276 | Slapd and pen spewed lots of debugging info and ldapsearch returned: 277 | 278 | 8<--- 279 | # 280 | # filter: (objectclass=*) 281 | # requesting: namingContexts 282 | # 283 | 284 | # 285 | dn: 286 | namingContexts: o=Qbranch,c=SE 287 | 288 | # search result 289 | search: 2 290 | result: 0 Success 291 | 292 | # numResponses: 2 293 | # numEntries: 1 294 | 8<--- 295 | 296 | which was what I expected. 297 | 298 | Inspired by this success, I repeated the test: 299 | 300 | 8<--- 301 | /usr/local/libexec/slapd -d 255 -h ldap://localhost:389/ 302 | 303 | /usr/local/libexec/slapd -d 255 -h ldap://localhost:390/ 304 | 305 | pen -d -d -f 3890 localhost:389 localhost:390 306 | 307 | ldapsearch -H ldap://localhost:3890 \ 308 | -x -b '' -s base '(objectclass=*)' namingContexts 309 | 8<--- 310 | 311 | Got a reply. Repeated a few times, then pressed Ctrl-C on the slapd that 312 | was serving the replies. Did another ldapsearch and got a reply from the 313 | other slapd. 314 | 315 | 316 | As far as I can see, pen handles LDAP load balancing and failover just 317 | fine. 318 | 319 | 320 | VRRP 321 | ---- 322 | 323 | It is possible to install pen on two load balancers and use vrrp 324 | to get failover in case one of them goes down. To do this, you need 325 | two servers (obviously), pen and Jerome Etienne's vrrpd for Linux. 326 | 327 | Install pen and vrrpd on both servers. Start pen on both servers, 328 | using all the same parameters and listening on all addresses. 329 | Finally start vrrpd, again using the same parameters on both 330 | hosts. Example, using debugging output to show what is going on: 331 | 332 | pen -df 2323 192.168.1.240:23 333 | vrrpd -i eth0 -v 1 192.168.1.199 334 | 335 | Now try telnetting to 192.168.1.199 on port 2323. You should get 336 | connected through pen on one of the servers. Log out and stop 337 | vrrpd on the active server. Again telnet to 192.168.1.199 port 2323. 338 | You should get connected again, this time through pen on the other 339 | server. 340 | 341 | Note that this doesn't provide perfect fault tolerance. Vrrpd 342 | only checks if the other server is alive, but it doesn't care 343 | if pen is responding. 344 | 345 | Also note that the legal status of vrrp is unclear, as both 346 | Cisco and IBM claim to hold patents on the technology. 347 | 348 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | 2 | Installing Pen 3 | ============== 4 | 5 | Option 1. Installing from a source tarball 6 | 7 | To compile and install using all defaults, type: 8 | 9 | ./configure 10 | make 11 | make install 12 | 13 | See also https://github.com/UlricE/pen/wiki/Building-Pen-from-Source 14 | 15 | Option 2. Installing from Git 16 | 17 | ./bootstrap.sh 18 | ./configure 19 | make 20 | make install 21 | 22 | See also https://github.com/UlricE/pen/wiki/Building-Pen-from-Git 23 | 24 | 25 | Options 26 | ------- 27 | 28 | TL;DR: ./configure without options is usually sufficient to figure out 29 | what is available and use the best combination. 30 | 31 | 32 | Most of the "generic" options are verbosely described below. Options 33 | that are specific to Pen include: 34 | 35 | --enable-profiling enable profiling 36 | 37 | Enable profiling with gprof. Most people have no interest in this. 38 | Disabled by default. 39 | 40 | --enable-debugging enable debugging messages 41 | 42 | With this enabled, more detailed diagnostics are built into Pen. 43 | Most people should have an interest in this, should something 44 | go wrong. Enabled by default. 45 | 46 | --with-geoip use libgeoip 47 | 48 | Build Pen with support for the GeoIP library. This provides access 49 | control lists that use country codes to permit or deny access. 50 | Built by default if available. 51 | 52 | --with-ssl=DIR use SSL (default /usr/local/ssl) 53 | 54 | Build Pen with SSL support. Built by default if available. 55 | 56 | --with-daemon use daemon() if available 57 | 58 | The --with-daemon option specifies how Pen puts itself in the background. 59 | Some operating systems have a function called daemon() which does this; 60 | with this option, the function will be used. Built by default if available. 61 | 62 | --with-poll use poll() if available 63 | 64 | Normally Pen uses the best available function to get notification 65 | when a socket is ready for reading or writing. On *BSD that is kqueue; 66 | on Linux it is epoll; on other Unix it is poll; on Windows it is select. 67 | This option enables using the poll() function. 68 | Built by default if available. 69 | 70 | --with-kqueue use kqueue() if available 71 | 72 | The kqueue interface for event notification is available on *bsd 73 | and offers better performance than either select() or poll(). 74 | Used by default if available. 75 | 76 | --with-epoll use epoll if available 77 | 78 | The epoll interface serves the same purpose on Linux as kqueue on BSD 79 | and is used by default if available. 80 | 81 | --with-fd_setsize=N set FD_SETSIZE to N 82 | 83 | (This is only relevant if you use select() as the event notification 84 | mechanism. You will probably use something else, except on Windows 85 | where only select() is available. If not on Windows, consider this 86 | section irrelevant.) 87 | 88 | To use Pen with a very large number of simultaneous connections, the 89 | preprocessing macro FD_SETSIZE must be increased. The default value varies 90 | between operating systems, as does the maximum supported. Pen will try to 91 | catch attempts to run out of spec. 92 | 93 | The select() used by Linux is broken in this respect, and doesn't 94 | allow FD_SETSIZE to be changed from the default. The solution is 95 | to not use Linux, or to change the #defines in the relevant include 96 | files: and . Look for lines 97 | like this: 98 | 99 | #define __FD_SETSIZE 1024 100 | 101 | and change them to what you really want, such as: 102 | 103 | #define __FD_SETSIZE 16384 104 | 105 | You probably want to change them back afterwards. 106 | 107 | The resulting max number of simultaneous connections will be: 108 | 109 | (FD_SETSIZE-10)/2 110 | 111 | 10 is for stdin, stdout, stderr "and then some". 112 | 2 is because each connection uses two file descriptors. 113 | 114 | The default FD_SETSIZE on Windows is very low, only 64. Fortunately, 115 | this value isn't cast in stone like it is on Linux. Just do: 116 | 117 | ./configure --with-fd_setsize=1024 118 | 119 | This will allow 507 simultaneous connections. 120 | 121 | --------------------------------------------------------------------- 122 | 123 | Basic Installation 124 | ================== 125 | 126 | These are generic installation instructions. 127 | 128 | The `configure' shell script attempts to guess correct values for 129 | various system-dependent variables used during compilation. It uses 130 | those values to create a `Makefile' in each directory of the package. 131 | It may also create one or more `.h' files containing system-dependent 132 | definitions. Finally, it creates a shell script `config.status' that 133 | you can run in the future to recreate the current configuration, a file 134 | `config.cache' that saves the results of its tests to speed up 135 | reconfiguring, and a file `config.log' containing compiler output 136 | (useful mainly for debugging `configure'). 137 | 138 | If you need to do unusual things to compile the package, please try 139 | to figure out how `configure' could check whether to do them, and mail 140 | diffs or instructions to the address given in the `README' so they can 141 | be considered for the next release. If at some point `config.cache' 142 | contains results you don't want to keep, you may remove or edit it. 143 | 144 | The file `configure.in' is used to create `configure' by a program 145 | called `autoconf'. You only need `configure.in' if you want to change 146 | it or regenerate `configure' using a newer version of `autoconf'. 147 | 148 | The simplest way to compile this package is: 149 | 150 | 1. `cd' to the directory containing the package's source code and type 151 | `./configure' to configure the package for your system. If you're 152 | using `csh' on an old version of System V, you might need to type 153 | `sh ./configure' instead to prevent `csh' from trying to execute 154 | `configure' itself. 155 | 156 | Running `configure' takes awhile. While running, it prints some 157 | messages telling which features it is checking for. 158 | 159 | 2. Type `make' to compile the package. 160 | 161 | 3. Optionally, type `make check' to run any self-tests that come with 162 | the package. 163 | 164 | 4. Type `make install' to install the programs and any data files and 165 | documentation. 166 | 167 | 5. You can remove the program binaries and object files from the 168 | source code directory by typing `make clean'. To also remove the 169 | files that `configure' created (so you can compile the package for 170 | a different kind of computer), type `make distclean'. There is 171 | also a `make maintainer-clean' target, but that is intended mainly 172 | for the package's developers. If you use it, you may have to get 173 | all sorts of other programs in order to regenerate files that came 174 | with the distribution. 175 | 176 | Compilers and Options 177 | ===================== 178 | 179 | Some systems require unusual options for compilation or linking that 180 | the `configure' script does not know about. You can give `configure' 181 | initial values for variables by setting them in the environment. Using 182 | a Bourne-compatible shell, you can do that on the command line like 183 | this: 184 | CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure 185 | 186 | Or on systems that have the `env' program, you can do it like this: 187 | env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure 188 | 189 | Compiling For Multiple Architectures 190 | ==================================== 191 | 192 | You can compile the package for more than one kind of computer at the 193 | same time, by placing the object files for each architecture in their 194 | own directory. To do this, you must use a version of `make' that 195 | supports the `VPATH' variable, such as GNU `make'. `cd' to the 196 | directory where you want the object files and executables to go and run 197 | the `configure' script. `configure' automatically checks for the 198 | source code in the directory that `configure' is in and in `..'. 199 | 200 | If you have to use a `make' that does not supports the `VPATH' 201 | variable, you have to compile the package for one architecture at a time 202 | in the source code directory. After you have installed the package for 203 | one architecture, use `make distclean' before reconfiguring for another 204 | architecture. 205 | 206 | Installation Names 207 | ================== 208 | 209 | By default, `make install' will install the package's files in 210 | `/usr/local/bin', `/usr/local/man', etc. You can specify an 211 | installation prefix other than `/usr/local' by giving `configure' the 212 | option `--prefix=PATH'. 213 | 214 | You can specify separate installation prefixes for 215 | architecture-specific files and architecture-independent files. If you 216 | give `configure' the option `--exec-prefix=PATH', the package will use 217 | PATH as the prefix for installing programs and libraries. 218 | Documentation and other data files will still use the regular prefix. 219 | 220 | In addition, if you use an unusual directory layout you can give 221 | options like `--bindir=PATH' to specify different values for particular 222 | kinds of files. Run `configure --help' for a list of the directories 223 | you can set and what kinds of files go in them. 224 | 225 | If the package supports it, you can cause programs to be installed 226 | with an extra prefix or suffix on their names by giving `configure' the 227 | option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. 228 | 229 | Optional Features 230 | ================= 231 | 232 | Some packages pay attention to `--enable-FEATURE' options to 233 | `configure', where FEATURE indicates an optional part of the package. 234 | They may also pay attention to `--with-PACKAGE' options, where PACKAGE 235 | is something like `gnu-as' or `x' (for the X Window System). The 236 | `README' should mention any `--enable-' and `--with-' options that the 237 | package recognizes. 238 | 239 | For packages that use the X Window System, `configure' can usually 240 | find the X include and library files automatically, but if it doesn't, 241 | you can use the `configure' options `--x-includes=DIR' and 242 | `--x-libraries=DIR' to specify their locations. 243 | 244 | Specifying the System Type 245 | ========================== 246 | 247 | There may be some features `configure' can not figure out 248 | automatically, but needs to determine by the type of host the package 249 | will run on. Usually `configure' can figure that out, but if it prints 250 | a message saying it can not guess the host type, give it the 251 | `--host=TYPE' option. TYPE can either be a short name for the system 252 | type, such as `sun4', or a canonical name with three fields: 253 | CPU-COMPANY-SYSTEM 254 | 255 | See the file `config.sub' for the possible values of each field. If 256 | `config.sub' isn't included in this package, then this package doesn't 257 | need to know the host type. 258 | 259 | If you are building compiler tools for cross-compiling, you can also 260 | use the `--target=TYPE' option to select the type of system they will 261 | produce code for and the `--build=TYPE' option to select the type of 262 | system on which you are compiling the package. 263 | 264 | Sharing Defaults 265 | ================ 266 | 267 | If you want to set default values for `configure' scripts to share, 268 | you can create a site shell script called `config.site' that gives 269 | default values for variables like `CC', `cache_file', and `prefix'. 270 | `configure' looks for `PREFIX/share/config.site' if it exists, then 271 | `PREFIX/etc/config.site' if it exists. Or, you can set the 272 | `CONFIG_SITE' environment variable to the location of the site script. 273 | A warning: not all `configure' scripts look for a site script. 274 | 275 | Operation Controls 276 | ================== 277 | 278 | `configure' recognizes the following options to control how it 279 | operates. 280 | 281 | `--cache-file=FILE' 282 | Use and save the results of the tests in FILE instead of 283 | `./config.cache'. Set FILE to `/dev/null' to disable caching, for 284 | debugging `configure'. 285 | 286 | `--help' 287 | Print a summary of the options to `configure', and exit. 288 | 289 | `--quiet' 290 | `--silent' 291 | `-q' 292 | Do not print messages saying which checks are being made. To 293 | suppress all normal output, redirect it to `/dev/null' (any error 294 | messages will still be shown). 295 | 296 | `--srcdir=DIR' 297 | Look for the package's source code in directory DIR. Usually 298 | `configure' can determine that directory automatically. 299 | 300 | `--version' 301 | Print the version of Autoconf used to generate the `configure' 302 | script, and exit. 303 | 304 | `configure' also accepts some other, not widely useful, options. 305 | 306 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The GNU General Public License, Version 2, June 1991 (GPLv2) 2 | ============================================================ 3 | 4 | > Copyright (C) 1989, 1991 Free Software Foundation, Inc. 5 | > 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 6 | 7 | Everyone is permitted to copy and distribute verbatim copies of this license 8 | document, but changing it is not allowed. 9 | 10 | 11 | Preamble 12 | -------- 13 | 14 | The licenses for most software are designed to take away your freedom to share 15 | and change it. By contrast, the GNU General Public License is intended to 16 | guarantee your freedom to share and change free software--to make sure the 17 | software is free for all its users. This General Public License applies to most 18 | of the Free Software Foundation's software and to any other program whose 19 | authors commit to using it. (Some other Free Software Foundation software is 20 | covered by the GNU Lesser General Public License instead.) You can apply it to 21 | your programs, too. 22 | 23 | When we speak of free software, we are referring to freedom, not price. Our 24 | General Public Licenses are designed to make sure that you have the freedom to 25 | distribute copies of free software (and charge for this service if you wish), 26 | that you receive source code or can get it if you want it, that you can change 27 | the software or use pieces of it in new free programs; and that you know you can 28 | do these things. 29 | 30 | To protect your rights, we need to make restrictions that forbid anyone to deny 31 | you these rights or to ask you to surrender the rights. These restrictions 32 | translate to certain responsibilities for you if you distribute copies of the 33 | software, or if you modify it. 34 | 35 | For example, if you distribute copies of such a program, whether gratis or for a 36 | fee, you must give the recipients all the rights that you have. You must make 37 | sure that they, too, receive or can get the source code. And you must show them 38 | these terms so they know their rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and (2) offer 41 | you this license which gives you legal permission to copy, distribute and/or 42 | modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain that 45 | everyone understands that there is no warranty for this free software. If the 46 | software is modified by someone else and passed on, we want its recipients to 47 | know that what they have is not the original, so that any problems introduced by 48 | others will not reflect on the original authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software patents. We wish 51 | to avoid the danger that redistributors of a free program will individually 52 | obtain patent licenses, in effect making the program proprietary. To prevent 53 | this, we have made it clear that any patent must be licensed for everyone's free 54 | use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and modification 57 | follow. 58 | 59 | 60 | Terms And Conditions For Copying, Distribution And Modification 61 | --------------------------------------------------------------- 62 | 63 | **0.** This License applies to any program or other work which contains a notice 64 | placed by the copyright holder saying it may be distributed under the terms of 65 | this General Public License. The "Program", below, refers to any such program or 66 | work, and a "work based on the Program" means either the Program or any 67 | derivative work under copyright law: that is to say, a work containing the 68 | Program or a portion of it, either verbatim or with modifications and/or 69 | translated into another language. (Hereinafter, translation is included without 70 | limitation in the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not covered by 73 | this License; they are outside its scope. The act of running the Program is not 74 | restricted, and the output from the Program is covered only if its contents 75 | constitute a work based on the Program (independent of having been made by 76 | running the Program). Whether that is true depends on what the Program does. 77 | 78 | **1.** You may copy and distribute verbatim copies of the Program's source code 79 | as you receive it, in any medium, provided that you conspicuously and 80 | appropriately publish on each copy an appropriate copyright notice and 81 | disclaimer of warranty; keep intact all the notices that refer to this License 82 | and to the absence of any warranty; and give any other recipients of the Program 83 | a copy of this License along with the Program. 84 | 85 | You may charge a fee for the physical act of transferring a copy, and you may at 86 | your option offer warranty protection in exchange for a fee. 87 | 88 | **2.** You may modify your copy or copies of the Program or any portion of it, 89 | thus forming a work based on the Program, and copy and distribute such 90 | modifications or work under the terms of Section 1 above, provided that you also 91 | meet all of these conditions: 92 | 93 | * **a)** You must cause the modified files to carry prominent notices stating 94 | that you changed the files and the date of any change. 95 | 96 | * **b)** You must cause any work that you distribute or publish, that in whole 97 | or in part contains or is derived from the Program or any part thereof, to 98 | be licensed as a whole at no charge to all third parties under the terms of 99 | this License. 100 | 101 | * **c)** If the modified program normally reads commands interactively when 102 | run, you must cause it, when started running for such interactive use in the 103 | most ordinary way, to print or display an announcement including an 104 | appropriate copyright notice and a notice that there is no warranty (or 105 | else, saying that you provide a warranty) and that users may redistribute 106 | the program under these conditions, and telling the user how to view a copy 107 | of this License. (Exception: if the Program itself is interactive but does 108 | not normally print such an announcement, your work based on the Program is 109 | not required to print an announcement.) 110 | 111 | These requirements apply to the modified work as a whole. If identifiable 112 | sections of that work are not derived from the Program, and can be reasonably 113 | considered independent and separate works in themselves, then this License, and 114 | its terms, do not apply to those sections when you distribute them as separate 115 | works. But when you distribute the same sections as part of a whole which is a 116 | work based on the Program, the distribution of the whole must be on the terms of 117 | this License, whose permissions for other licensees extend to the entire whole, 118 | and thus to each and every part regardless of who wrote it. 119 | 120 | Thus, it is not the intent of this section to claim rights or contest your 121 | rights to work written entirely by you; rather, the intent is to exercise the 122 | right to control the distribution of derivative or collective works based on the 123 | Program. 124 | 125 | In addition, mere aggregation of another work not based on the Program with the 126 | Program (or with a work based on the Program) on a volume of a storage or 127 | distribution medium does not bring the other work under the scope of this 128 | License. 129 | 130 | **3.** You may copy and distribute the Program (or a work based on it, under 131 | Section 2) in object code or executable form under the terms of Sections 1 and 2 132 | above provided that you also do one of the following: 133 | 134 | * **a)** Accompany it with the complete corresponding machine-readable source 135 | code, which must be distributed under the terms of Sections 1 and 2 above on 136 | a medium customarily used for software interchange; or, 137 | 138 | * **b)** Accompany it with a written offer, valid for at least three years, to 139 | give any third party, for a charge no more than your cost of physically 140 | performing source distribution, a complete machine-readable copy of the 141 | corresponding source code, to be distributed under the terms of Sections 1 142 | and 2 above on a medium customarily used for software interchange; or, 143 | 144 | * **c)** Accompany it with the information you received as to the offer to 145 | distribute corresponding source code. (This alternative is allowed only for 146 | noncommercial distribution and only if you received the program in object 147 | code or executable form with such an offer, in accord with Subsection b 148 | above.) 149 | 150 | The source code for a work means the preferred form of the work for making 151 | modifications to it. For an executable work, complete source code means all the 152 | source code for all modules it contains, plus any associated interface 153 | definition files, plus the scripts used to control compilation and installation 154 | of the executable. However, as a special exception, the source code distributed 155 | need not include anything that is normally distributed (in either source or 156 | binary form) with the major components (compiler, kernel, and so on) of the 157 | operating system on which the executable runs, unless that component itself 158 | accompanies the executable. 159 | 160 | In addition, as a special exception, the copyright holders give 161 | permission to link the code of portions of this program with the 162 | OpenSSL library under certain conditions as described in each 163 | individual source file, and distribute linked combinations 164 | including the two. 165 | You must obey the GNU General Public License in all respects 166 | for all of the code used other than OpenSSL. If you modify 167 | file(s) with this exception, you may extend this exception to your 168 | version of the file(s), but you are not obligated to do so. If you 169 | do not wish to do so, delete this exception statement from your 170 | version. If you delete this exception statement from all source 171 | files in the program, then also delete it here. 172 | 173 | If distribution of executable or object code is made by offering access to copy 174 | from a designated place, then offering equivalent access to copy the source code 175 | from the same place counts as distribution of the source code, even though third 176 | parties are not compelled to copy the source along with the object code. 177 | 178 | **4.** You may not copy, modify, sublicense, or distribute the Program except as 179 | expressly provided under this License. Any attempt otherwise to copy, modify, 180 | sublicense or distribute the Program is void, and will automatically terminate 181 | your rights under this License. However, parties who have received copies, or 182 | rights, from you under this License will not have their licenses terminated so 183 | long as such parties remain in full compliance. 184 | 185 | **5.** You are not required to accept this License, since you have not signed 186 | it. However, nothing else grants you permission to modify or distribute the 187 | Program or its derivative works. These actions are prohibited by law if you do 188 | not accept this License. Therefore, by modifying or distributing the Program (or 189 | any work based on the Program), you indicate your acceptance of this License to 190 | do so, and all its terms and conditions for copying, distributing or modifying 191 | the Program or works based on it. 192 | 193 | **6.** Each time you redistribute the Program (or any work based on the 194 | Program), the recipient automatically receives a license from the original 195 | licensor to copy, distribute or modify the Program subject to these terms and 196 | conditions. You may not impose any further restrictions on the recipients' 197 | exercise of the rights granted herein. You are not responsible for enforcing 198 | compliance by third parties to this License. 199 | 200 | **7.** If, as a consequence of a court judgment or allegation of patent 201 | infringement or for any other reason (not limited to patent issues), conditions 202 | are imposed on you (whether by court order, agreement or otherwise) that 203 | contradict the conditions of this License, they do not excuse you from the 204 | conditions of this License. If you cannot distribute so as to satisfy 205 | simultaneously your obligations under this License and any other pertinent 206 | obligations, then as a consequence you may not distribute the Program at all. 207 | For example, if a patent license would not permit royalty-free redistribution of 208 | the Program by all those who receive copies directly or indirectly through you, 209 | then the only way you could satisfy both it and this License would be to refrain 210 | entirely from distribution of the Program. 211 | 212 | If any portion of this section is held invalid or unenforceable under any 213 | particular circumstance, the balance of the section is intended to apply and the 214 | section as a whole is intended to apply in other circumstances. 215 | 216 | It is not the purpose of this section to induce you to infringe any patents or 217 | other property right claims or to contest validity of any such claims; this 218 | section has the sole purpose of protecting the integrity of the free software 219 | distribution system, which is implemented by public license practices. Many 220 | people have made generous contributions to the wide range of software 221 | distributed through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing to 223 | distribute software through any other system and a licensee cannot impose that 224 | choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to be a 227 | consequence of the rest of this License. 228 | 229 | **8.** If the distribution and/or use of the Program is restricted in certain 230 | countries either by patents or by copyrighted interfaces, the original copyright 231 | holder who places the Program under this License may add an explicit 232 | geographical distribution limitation excluding those countries, so that 233 | distribution is permitted only in or among countries not thus excluded. In such 234 | case, this License incorporates the limitation as if written in the body of this 235 | License. 236 | 237 | **9.** The Free Software Foundation may publish revised and/or new versions of 238 | the General Public License from time to time. Such new versions will be similar 239 | in spirit to the present version, but may differ in detail to address new 240 | problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program specifies 243 | a version number of this License which applies to it and "any later version", 244 | you have the option of following the terms and conditions either of that version 245 | or of any later version published by the Free Software Foundation. If the 246 | Program does not specify a version number of this License, you may choose any 247 | version ever published by the Free Software Foundation. 248 | 249 | **10.** If you wish to incorporate parts of the Program into other free programs 250 | whose distribution conditions are different, write to the author to ask for 251 | permission. For software which is copyrighted by the Free Software Foundation, 252 | write to the Free Software Foundation; we sometimes make exceptions for this. 253 | Our decision will be guided by the two goals of preserving the free status of 254 | all derivatives of our free software and of promoting the sharing and reuse of 255 | software generally. 256 | 257 | 258 | No Warranty 259 | ----------- 260 | 261 | **11.** BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR 262 | THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE 263 | STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM 264 | "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, 265 | BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 266 | PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 267 | PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 268 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 269 | 270 | **12.** IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE 272 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 273 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR 274 | INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA 275 | BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 276 | FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER 277 | OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 278 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | 2 | bin_PROGRAMS = pen mergelogs penctl penlog penlogd 3 | noinst_HEADERS = pen.h dlist.h ssl.h dsr.h memory.h diag.h settings.h acl.h event.h pen_select.h pen_poll.h pen_kqueue.h pen_epoll.h netconv.h windows.h server.h client.h conn.h idlers.h 4 | 5 | pen_SOURCES = pen.c select.c poll.c kqueue.c epoll.c dlist.c ssl.c dsr.c memory.c diag.c settings.c acl.c event.c netconv.c server.c client.c conn.c idlers.c 6 | 7 | mergelogs_SOURCES = mergelogs.c memory.c diag.c settings.c 8 | penctl_SOURCES = penctl.c 9 | penlog_SOURCES = penlog.c diag.c settings.c 10 | penlogd_SOURCES = penlogd.c diag.c settings.c 11 | 12 | doc_DATA = penstats HOWTO AUTHORS README ChangeLog COPYING 13 | 14 | man_MANS = pen.1 mergelogs.1 penctl.1 penlog.1 penlogd.1 15 | 16 | EXTRA_DIST = $(man_MANS) $(doc_DATA) penctl.cgi pen.spec siag.pem \ 17 | Makefile.win X86/Makefile X64/Makefile windows.c config.h.win \ 18 | pen-ocsp.sh 19 | 20 | tgz: 21 | rm -rf tmpinst 22 | mkdir tmpinst 23 | make clean 24 | ./configure --prefix=/usr 25 | make 26 | make install-strip prefix=tmpinst/usr 27 | (cd tmpinst && makepkg pen.tgz && mv pen.tgz ..) 28 | 29 | -------------------------------------------------------------------------------- /Makefile.win: -------------------------------------------------------------------------------- 1 | CFLAGS=-Wall -O2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -g -ggdb -DWINDOWS -D_WIN32_WINNT=0x0600 -DDEBUGGING -DFD_SETSIZE=1024 2 | OBJ=pen.o select.o poll.o kqueue.o epoll.o dlist.o ssl.o dsr.o memory.o diag.o settings.o acl.o event.o netconv.o server.o client.o conn.o idlers.o windows.o 3 | LIB=-lws2_32 -lpsapi -lole32 -loleaut32 -luuid 4 | 5 | all: 6 | cp config.h.win config.h 7 | $(MAKE) -C X86 pen32.exe 8 | $(MAKE) -C X64 pen64.exe 9 | 10 | pen32.exe: $(OBJ) 11 | $(CC) -o pen32.exe $(OBJ) $(LIB) 12 | 13 | pen64.exe: $(OBJ) 14 | $(CC) -o pen64.exe $(OBJ) $(LIB) 15 | 16 | pen.o: pen.c 17 | select.o: select.c 18 | poll.o: poll.c 19 | kqueue.o: kqueue.c 20 | epoll.o: epoll.c 21 | windows.o: windows.c 22 | dlist.o: dlist.c 23 | 24 | $(OBJ): pen.h config.h 25 | 26 | clean: 27 | rm -f X86/*.o X64/*.o 28 | 29 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UlricE/pen/2c09b7a82122836ee26e6c7e6a141d0b44b724a6/NEWS -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | 2 | This is pen, a load balancer for udp and tcp based protocols such as 3 | dns, http or smtp. It allows several servers to appear as one to the 4 | outside and automatically detects servers that are down and distributes 5 | clients among the available servers. This gives high availability and 6 | scalable performance. 7 | 8 | The load balancing algorithm keeps track of clients and will try to 9 | send them back to the server they visited the last time. The client 10 | table has a number of slots (default 2048, settable through command-line 11 | arguments). When the table is full, the least recently used one will 12 | be thrown out to make room for the new one. 13 | 14 | This is superior to a simple round-robin algorithm, which sends a client 15 | that connects repeatedly to different servers. Doing so breaks 16 | applications that maintain state between connections in the server, 17 | including most modern web applications. 18 | 19 | When pen detects that a server is unavailable, it scans for another 20 | starting with the server after the most recently used one. That way 21 | we get load balancing and "fair" failover for free. 22 | 23 | Correctly configured, pen can ensure that a server farm is always 24 | available, even when individual servers are brought down for maintenance 25 | or reconfiguration. The final single point of failure, pen itself, 26 | can be eliminated by running pen on several servers, using vrrp to 27 | decide which is active. 28 | 29 | 30 | A side-effect of load-balancing web servers is that several logfiles 31 | are produced, and by default, Pen operates in a proxy mode that makes 32 | all accesses seem to come from the load balancer. 33 | The program penlogd solves this problem by merging pen's log file 34 | with the ones produced by the web servers. See penlogd(1) and penlog(1) 35 | for details. 36 | 37 | The Direct Server Return and Transparent Reverse Proxy modes make 38 | accesses seem to come directly from the client. Multiple logfiles 39 | are still created, and Penlogd can still be used to consolidate them 40 | automatically. 41 | 42 | 43 | This load balancer is known to work on FreeBSD, Linux, HP-UX and Solaris. 44 | Other Unixes should work as well, possibly requiring trivial changes. 45 | Success stories or problem reports are welcome. 46 | 47 | 48 | If pen is started with the -w option, statistics are saved in html 49 | format when a USR1 signal is received. The cgi script penstats can 50 | be used to simplify this; it must be edited to reflect local conditions. 51 | 52 | 53 | I am Ulric Eriksson, ulric@siag.nu. 54 | 55 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UlricE/pen/2c09b7a82122836ee26e6c7e6a141d0b44b724a6/TODO -------------------------------------------------------------------------------- /X64/Makefile: -------------------------------------------------------------------------------- 1 | 2 | VPATH=.. 3 | CC=x86_64-w64-mingw32-gcc -m64 4 | include ../Makefile.win 5 | -------------------------------------------------------------------------------- /X86/Makefile: -------------------------------------------------------------------------------- 1 | 2 | VPATH=.. 3 | CC=i686-w64-mingw32-gcc -m32 4 | include ../Makefile.win 5 | -------------------------------------------------------------------------------- /acl.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #ifdef WINDOWS 6 | #include 7 | #else 8 | #include 9 | #include 10 | #include 11 | #endif 12 | #ifdef HAVE_LIBGEOIP 13 | #include 14 | GeoIP *geoip4, *geoip6; 15 | #endif 16 | 17 | #include "acl.h" 18 | #include "diag.h" 19 | #include "memory.h" 20 | #include "netconv.h" 21 | #include "windows.h" 22 | 23 | #define ACE_IPV4 (1) 24 | #define ACE_IPV6 (2) 25 | #define ACE_GEO (3) 26 | 27 | static int nacls[ACLS_MAX]; 28 | static acl *acls[ACLS_MAX]; 29 | static unsigned char mask_ipv6[129][16]; 30 | 31 | static void init_mask(void) 32 | { 33 | unsigned char m6[16]; 34 | int i, j; 35 | 36 | memset(m6, 0, sizeof m6); 37 | for (i = 0; i < 129; i++) { 38 | for (j = 15; j >= 0; j--) { 39 | mask_ipv6[i][j] = m6[j]; 40 | m6[j] >>= 1; 41 | if (j > 0) { 42 | m6[j] |= (m6[j-1] << 7); 43 | } else { 44 | m6[j] |= (1 << 7); 45 | } 46 | } 47 | } 48 | } 49 | 50 | /* allocate ace and fill in the generics */ 51 | static int add_acl(int a, unsigned char permit) 52 | { 53 | int i; 54 | if (a < 0 || a >= ACLS_MAX) { 55 | debug("add_acl: %d outside (0,%d)", a, ACLS_MAX); 56 | return -1; 57 | } 58 | i = nacls[a]++; 59 | acls[a] = pen_realloc(acls[a], nacls[a]*sizeof(acl)); 60 | acls[a][i].permit = permit; 61 | return i; 62 | } 63 | 64 | void add_acl_ipv4(int a, unsigned int ip, unsigned int mask, unsigned char permit) 65 | { 66 | int i = add_acl(a, permit); 67 | 68 | if (i == -1) return; 69 | 70 | DEBUG(2, "add_acl_ipv4(%d, %x, %x, %d)", a, ip, mask, permit); 71 | acls[a][i].class = ACE_IPV4; 72 | acls[a][i].ace.ipv4.ip = ip; 73 | acls[a][i].ace.ipv4.mask = mask; 74 | } 75 | 76 | void add_acl_ipv6(int a, unsigned char *ipaddr, unsigned char len, unsigned char permit) 77 | { 78 | int i = add_acl(a, permit); 79 | 80 | if (i == -1) return; 81 | 82 | DEBUG(2, "add_acl_ipv6(%d, %x, %d, %d)\n" \ 83 | "%x:%x:%x:%x:%x:%x:%x:%x/%d", \ 84 | a, ipaddr, len, permit, \ 85 | 256*ipaddr[0]+ipaddr[1], 256*ipaddr[2]+ipaddr[3], 256*ipaddr[4]+ipaddr[5], 256*ipaddr[6]+ipaddr[7], \ 86 | 256*ipaddr[8]+ipaddr[9], 256*ipaddr[10]+ipaddr[11], 256*ipaddr[12]+ipaddr[13], 256*ipaddr[14]+ipaddr[15], len); 87 | acls[a][i].class = ACE_IPV6; 88 | memcpy(acls[a][i].ace.ipv6.ip.s6_addr, ipaddr, 16); 89 | acls[a][i].ace.ipv6.len = len; 90 | } 91 | 92 | void add_acl_geo(int a, char *country, unsigned char permit) 93 | { 94 | int i = add_acl(a, permit); 95 | 96 | if (i == -1) return; 97 | 98 | DEBUG(2, "add_acl_geo(%d, %s, %d", a, country, permit); 99 | acls[a][i].class = ACE_GEO; 100 | strncpy(acls[a][i].ace.geo.country, country, 2); 101 | } 102 | 103 | void del_acl(int a) 104 | { 105 | DEBUG(2, "del_acl(%d)", a); 106 | if (a < 0 || a >= ACLS_MAX) { 107 | debug("del_acl: %d outside (0,%d)", a, ACLS_MAX); 108 | return; 109 | } 110 | free(acls[a]); 111 | acls[a] = NULL; 112 | nacls[a] = 0; 113 | } 114 | 115 | #ifndef WINDOWS 116 | static int match_acl_unix(int a, struct sockaddr_un *cli_addr) 117 | { 118 | DEBUG(2, "Unix acl:s not implemented"); 119 | return 1; 120 | } 121 | #endif 122 | 123 | static int match_acl_ipv4(int a, struct sockaddr_in *cli_addr) 124 | { 125 | unsigned int client = cli_addr->sin_addr.s_addr; 126 | int i; 127 | int permit = 0; 128 | acl *ap = acls[a]; 129 | #ifdef HAVE_LIBGEOIP 130 | const char *country = NULL; 131 | int geo_done = 0; 132 | #endif 133 | DEBUG(2, "match_acl_ipv4(%d, %u)", a, client); 134 | for (i = 0; i < nacls[a]; i++) { 135 | permit = ap[i].permit; 136 | switch (ap[i].class) { 137 | case ACE_IPV4: 138 | if ((client & ap[i].ace.ipv4.mask) == ap[i].ace.ipv4.ip) { 139 | return permit; 140 | } 141 | break; 142 | case ACE_GEO: 143 | #ifdef HAVE_LIBGEOIP 144 | if (geoip4 == NULL) break; 145 | if (!geo_done) { 146 | country = GeoIP_country_code_by_addr(geoip4, 147 | pen_ntoa((struct sockaddr_storage *)cli_addr)); 148 | DEBUG(2, "Country = %s", country?country:"unknown"); 149 | geo_done = 1; 150 | } 151 | if (country && !strncmp(country, 152 | ap[i].ace.geo.country, 2)) { 153 | return permit; 154 | } 155 | #else 156 | debug("ACE_GEO: Not implemented"); 157 | #endif 158 | break; 159 | default: 160 | /* ignore other ACE classes (ipv6 et al) */ 161 | break; 162 | } 163 | } 164 | return !permit; 165 | } 166 | 167 | /* The most straightforward way to get at the bytes of an ipv6 address 168 | is to take the pointer to the in6_addr and cast it to a pointer to 169 | unsigned char. 170 | */ 171 | static int match_acl_ipv6(int a, struct sockaddr_in6 *cli_addr) 172 | { 173 | unsigned char *client = (unsigned char *)&(cli_addr->sin6_addr); 174 | unsigned char *ip; 175 | unsigned char *mask; 176 | int len; 177 | int i, j; 178 | int permit = 0; 179 | acl *ap = acls[a]; 180 | #ifdef HAVE_LIBGEOIP 181 | const char *country = NULL; 182 | int geo_done = 0; 183 | #endif 184 | 185 | DEBUG(2, "match_acl_ipv6(%d, %u)", a, client); 186 | for (i = 0; i < nacls[a]; i++) { 187 | permit = ap[i].permit; 188 | switch (ap[i].class) { 189 | case ACE_IPV6: 190 | len = ap[i].ace.ipv6.len; 191 | ip = (unsigned char *)&(ap[i].ace.ipv6.ip); 192 | mask = mask_ipv6[len]; 193 | 194 | DEBUG(2, "Matching %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x against %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x / %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", \ 195 | client[0], client[1], client[2], client[3], \ 196 | client[4], client[5], client[6], client[7], \ 197 | client[8], client[9], client[10], client[11], \ 198 | client[12], client[13], client[14], client[15], \ 199 | ip[0], ip[1], ip[2], ip[3], \ 200 | ip[4], ip[5], ip[6], ip[7], \ 201 | ip[8], ip[9], ip[10], ip[11], \ 202 | ip[12], ip[13], ip[14], ip[15], \ 203 | mask[0], mask[1], mask[2], mask[3], \ 204 | mask[4], mask[5], mask[6], mask[7], \ 205 | mask[8], mask[9], mask[10], mask[11], \ 206 | mask[12], mask[13], mask[14], mask[15]); 207 | 208 | for (j = 0; j < 16; j++) { 209 | if ((client[j] & mask[j]) != ip[j]) break; 210 | } 211 | if (j == 16) return permit; 212 | break; 213 | case ACE_GEO: 214 | #ifdef HAVE_LIBGEOIP 215 | if (geoip6 == NULL) break; 216 | if (!geo_done) { 217 | country = GeoIP_country_code_by_addr_v6(geoip6, 218 | pen_ntoa((struct sockaddr_storage *)cli_addr)); 219 | DEBUG(2, "Country = %s", country?country:"unknown"); 220 | geo_done = 1; 221 | } 222 | if (country && !strncmp(country, 223 | ap[i].ace.geo.country, 2)) { 224 | return permit; 225 | } 226 | #else 227 | debug("ACE_GEO: Not implemented"); 228 | #endif 229 | break; 230 | default: 231 | /* ignore other ACE classes (ipv4 et al) */ 232 | break; 233 | } 234 | } 235 | return !permit; 236 | } 237 | 238 | /* returns nonzero if the acl is matched, zero otherwise */ 239 | int match_acl(int a, struct sockaddr_storage *cli_addr) 240 | { 241 | if (a < 0 || a > ACLS_MAX) return 0; /* acl out of bounds */ 242 | switch (cli_addr->ss_family) { 243 | #ifndef WINDOWS 244 | case AF_UNIX: 245 | return match_acl_unix(a, (struct sockaddr_un *)cli_addr); 246 | #endif 247 | case AF_INET: 248 | return match_acl_ipv4(a, (struct sockaddr_in *)cli_addr); 249 | case AF_INET6: 250 | return match_acl_ipv6(a, (struct sockaddr_in6 *)cli_addr); 251 | default: 252 | debug("match_acl: unknown address family %d", cli_addr->ss_family); 253 | } 254 | return 0; 255 | } 256 | 257 | void save_acls(FILE *fp) 258 | { 259 | int i, j; 260 | struct in_addr ip; 261 | char ip_str[INET6_ADDRSTRLEN]; 262 | for (i = 0; i < ACLS_MAX; i++) { 263 | fprintf(fp, "no acl %d\n", i); 264 | for (j = 0; j < nacls[i]; j++) { 265 | fprintf(fp, "acl %d %s ", i, 266 | acls[i][j].permit?"permit":"deny"); 267 | switch (acls[i][j].class) { 268 | case ACE_IPV4: 269 | memcpy(&ip, &acls[i][j].ace.ipv4.ip, 4); 270 | fprintf(fp, "%s ", inet_ntoa(ip)); 271 | memcpy(&ip, &acls[i][j].ace.ipv4.mask, 4); 272 | fprintf(fp, "%s\n", inet_ntoa(ip)); 273 | break; 274 | case ACE_IPV6: 275 | fprintf(fp, "%s/%d\n", 276 | inet_ntop(AF_INET6, 277 | &acls[i][j].ace.ipv6.ip, 278 | ip_str, sizeof ip_str), 279 | acls[i][j].ace.ipv6.len); 280 | break; 281 | case ACE_GEO: 282 | fprintf(fp, "country %c%c\n", 283 | acls[i][j].ace.geo.country[0], 284 | acls[i][j].ace.geo.country[1]); 285 | break; 286 | default: 287 | debug("Unknown ACE class %d (this is probably a bug)", 288 | acls[i][j].class); 289 | } 290 | } 291 | } 292 | } 293 | 294 | void acl_init(void) 295 | { 296 | init_mask(); 297 | #ifdef HAVE_LIBGEOIP 298 | geoip4 = GeoIP_open_type(GEOIP_COUNTRY_EDITION, GEOIP_MEMORY_CACHE); 299 | if (geoip4 == NULL) debug("Could not initialize GeoIP for IPv4"); 300 | geoip6 = GeoIP_open_type(GEOIP_COUNTRY_EDITION_V6, GEOIP_MEMORY_CACHE); 301 | if (geoip6 == NULL) debug("Could not initialize GeoIP for IPv6"); 302 | #endif 303 | } 304 | -------------------------------------------------------------------------------- /acl.h: -------------------------------------------------------------------------------- 1 | #ifndef WINDOWS 2 | #include 3 | #include /* for sockaddr_storage */ 4 | #else 5 | #include 6 | #include 7 | #ifndef INET6_ADDRSTRLEN 8 | #define INET6_ADDRSTRLEN 46 9 | #endif 10 | #endif 11 | 12 | #define ACLS_MAX 10 /* max acls */ 13 | 14 | typedef struct { 15 | unsigned int ip, mask; 16 | } ace_ipv4; 17 | 18 | typedef struct { 19 | struct in6_addr ip; 20 | unsigned char len; 21 | } ace_ipv6; 22 | 23 | typedef struct { 24 | char country[2]; 25 | } ace_geo; 26 | 27 | typedef struct { 28 | unsigned char class; 29 | unsigned char permit; 30 | union { 31 | ace_ipv4 ipv4; 32 | ace_ipv6 ipv6; 33 | ace_geo geo; 34 | } ace; 35 | } acl; 36 | 37 | extern int client_acl; 38 | 39 | extern void add_acl_ipv4(int, unsigned int, unsigned int, unsigned char); 40 | extern void add_acl_ipv6(int, unsigned char *, unsigned char, unsigned char); 41 | extern void add_acl_geo(int, char *, unsigned char); 42 | extern void del_acl(int); 43 | extern int match_acl(int, struct sockaddr_storage *); 44 | extern void save_acls(FILE *fp); 45 | extern void acl_init(void); 46 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | aclocal 4 | autoheader 5 | autoconf 6 | automake --copy --add-missing --foreign 7 | -------------------------------------------------------------------------------- /client.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #ifndef WINDOWS 4 | #include 5 | #endif 6 | #include "client.h" 7 | #include "conn.h" 8 | #include "diag.h" 9 | #include "memory.h" 10 | #include "netconv.h" 11 | #include "pen.h" 12 | #include "server.h" 13 | 14 | client *clients; 15 | int clients_max = 0; 16 | int client_acl; 17 | 18 | /* Store client and return index */ 19 | int store_client(struct sockaddr_storage *cli) 20 | { 21 | int i; 22 | int empty = -1; /* first empty slot */ 23 | int oldest = -1; /* in case we need to recycle */ 24 | struct sockaddr_in *si; 25 | struct sockaddr_in6 *si6; 26 | int family = cli->ss_family; 27 | unsigned long ad = 0; 28 | void *ad6 = 0; 29 | 30 | if (family == AF_INET) { 31 | si = (struct sockaddr_in *)cli; 32 | ad = si->sin_addr.s_addr; 33 | } else if (family == AF_INET6) { 34 | si6 = (struct sockaddr_in6 *)cli; 35 | ad6 = &si6->sin6_addr; 36 | } 37 | 38 | for (i = 0; i < clients_max; i++) { 39 | /* look for client with same family and address */ 40 | if (family == clients[i].addr.ss_family) { 41 | if (family == AF_UNIX) break; 42 | if (family == AF_INET) { 43 | si = (struct sockaddr_in *)&clients[i].addr; 44 | if (ad == si->sin_addr.s_addr) break; 45 | } 46 | if (family == AF_INET6) { 47 | si6 = (struct sockaddr_in6 *)&clients[i].addr; 48 | if (!memcmp(ad6, &si6->sin6_addr, sizeof *ad6)) break; 49 | } 50 | } 51 | 52 | /* recycle slots of client that haven't been used for some time */ 53 | if (tracking_time > 0 && clients[i].last+tracking_time < now) { 54 | /* too old, recycle */ 55 | clients[i].last = 0; 56 | } 57 | 58 | /* we already have an empty slot but keep looking for known client */ 59 | if (empty != -1) continue; 60 | 61 | /* remember this empty slot in case we need it later */ 62 | if (clients[i].last == 0) { 63 | empty = i; 64 | continue; 65 | } 66 | 67 | /* and if we can't find any reusable slot we'll reuse the oldest one */ 68 | if (oldest == -1 || (clients[i].last < clients[oldest].last)) { 69 | oldest = i; 70 | } 71 | } 72 | 73 | /* reset statistics in case this is a "new" client */ 74 | if (i == clients_max) { 75 | if (empty != -1) i = empty; 76 | else i = oldest; 77 | DEBUG(2, "Resetting client stats for slot %d", i); 78 | clients[i].connects = 0; 79 | clients[i].csx = 0; 80 | clients[i].crx = 0; 81 | clients[i].server = NO_SERVER; 82 | } 83 | 84 | clients[i].last = now; 85 | clients[i].addr = *cli; 86 | clients[i].connects++; 87 | 88 | 89 | DEBUG(2, "Client %s has index %d", pen_ntoa(cli), i); 90 | 91 | return i; 92 | } 93 | 94 | void expand_clienttable(int size) 95 | { 96 | if (size <= clients_max) return; /* nothing to do */ 97 | clients = pen_realloc(clients, size*sizeof *clients); 98 | memset(&clients[clients_max], 0, (size-clients_max)*sizeof clients[0]); 99 | clients_max = size; 100 | } 101 | 102 | -------------------------------------------------------------------------------- /client.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #ifndef WINDOWS 4 | #include 5 | #else 6 | #include 7 | #endif 8 | 9 | #define CLIENTS_MAX 2048 /* max clients */ 10 | #define TRACKING_TIME 0 /* how long a client is remembered */ 11 | 12 | typedef struct { 13 | time_t last; /* last time this client made a connection */ 14 | struct sockaddr_storage addr; 15 | int server; /* server used last time */ 16 | long connects; 17 | uint64_t csx, crx; 18 | } client; 19 | 20 | extern client *clients; 21 | extern int clients_max; 22 | 23 | extern int store_client(struct sockaddr_storage *); 24 | extern void expand_clienttable(int); 25 | -------------------------------------------------------------------------------- /config.h.in: -------------------------------------------------------------------------------- 1 | /* config.h.in. Generated from configure.ac by autoheader. */ 2 | 3 | /* #undef FD_SETSIZE */ 4 | #undef FD_SETSIZE 5 | 6 | /* #undef HAVE_ACCEPT4 */ 7 | #undef HAVE_ACCEPT4 8 | 9 | /* #undef HAVE_DAEMON */ 10 | #undef HAVE_DAEMON 11 | 12 | /* Define to 1 if you have the header file, and it defines `DIR'. 13 | */ 14 | #undef HAVE_DIRENT_H 15 | 16 | /* #undef HAVE_EC_KEY */ 17 | #undef HAVE_EC_KEY 18 | 19 | /* #undef HAVE_EPOLL */ 20 | #undef HAVE_EPOLL 21 | 22 | /* #undef HAVE_GETADDRINFO */ 23 | #undef HAVE_GETADDRINFO 24 | 25 | /* Define to 1 if you have the header file. */ 26 | #undef HAVE_INTTYPES_H 27 | 28 | /* #undef HAVE_KQUEUE */ 29 | #undef HAVE_KQUEUE 30 | 31 | /* Define to 1 if you have the `crypto' library (-lcrypto). */ 32 | #undef HAVE_LIBCRYPTO 33 | 34 | /* Define to 1 if you have the `GeoIP' library (-lGeoIP). */ 35 | #undef HAVE_LIBGEOIP 36 | 37 | /* Define to 1 if you have the `nsl' library (-lnsl). */ 38 | #undef HAVE_LIBNSL 39 | 40 | /* Define to 1 if you have the `resolv' library (-lresolv). */ 41 | #undef HAVE_LIBRESOLV 42 | 43 | /* Define to 1 if you have the `socket' library (-lsocket). */ 44 | #undef HAVE_LIBSOCKET 45 | 46 | /* Define to 1 if you have the `ssl' library (-lssl). */ 47 | #undef HAVE_LIBSSL 48 | 49 | /* Define to 1 if you have the header file. */ 50 | #undef HAVE_LINUX_IF_PACKET_H 51 | 52 | /* Define to 1 if you have the header file. */ 53 | #undef HAVE_MEMORY_H 54 | 55 | /* Define to 1 if you have the header file, and it defines `DIR'. */ 56 | #undef HAVE_NDIR_H 57 | 58 | /* Define to 1 if you have the header file. */ 59 | #undef HAVE_NET_NETMAP_USER_H 60 | 61 | /* #undef HAVE_POLL */ 62 | #undef HAVE_POLL 63 | 64 | /* Define to 1 if you have the header file. */ 65 | #undef HAVE_STDINT_H 66 | 67 | /* Define to 1 if you have the header file. */ 68 | #undef HAVE_STDLIB_H 69 | 70 | /* Define to 1 if you have the header file. */ 71 | #undef HAVE_STRINGS_H 72 | 73 | /* Define to 1 if you have the header file. */ 74 | #undef HAVE_STRING_H 75 | 76 | /* Define to 1 if you have the header file, and it defines `DIR'. 77 | */ 78 | #undef HAVE_SYS_DIR_H 79 | 80 | /* Define to 1 if you have the header file, and it defines `DIR'. 81 | */ 82 | #undef HAVE_SYS_NDIR_H 83 | 84 | /* Define to 1 if you have the header file. */ 85 | #undef HAVE_SYS_SELECT_H 86 | 87 | /* Define to 1 if you have the header file. */ 88 | #undef HAVE_SYS_STAT_H 89 | 90 | /* Define to 1 if you have the header file. */ 91 | #undef HAVE_SYS_TYPES_H 92 | 93 | /* Define to 1 if you have the header file. */ 94 | #undef HAVE_UNISTD_H 95 | 96 | /* Define to 1 if `major', `minor', and `makedev' are declared in . 97 | */ 98 | #undef MAJOR_IN_MKDEV 99 | 100 | /* Define to 1 if `major', `minor', and `makedev' are declared in 101 | . */ 102 | #undef MAJOR_IN_SYSMACROS 103 | 104 | /* Name of package */ 105 | #undef PACKAGE 106 | 107 | /* Define to the address where bug reports for this package should be sent. */ 108 | #undef PACKAGE_BUGREPORT 109 | 110 | /* Define to the full name of this package. */ 111 | #undef PACKAGE_NAME 112 | 113 | /* Define to the full name and version of this package. */ 114 | #undef PACKAGE_STRING 115 | 116 | /* Define to the one symbol short name of this package. */ 117 | #undef PACKAGE_TARNAME 118 | 119 | /* Define to the home page for this package. */ 120 | #undef PACKAGE_URL 121 | 122 | /* Define to the version of this package. */ 123 | #undef PACKAGE_VERSION 124 | 125 | /* Define to 1 if you have the ANSI C header files. */ 126 | #undef STDC_HEADERS 127 | 128 | /* Define to 1 if you can safely include both and . */ 129 | #undef TIME_WITH_SYS_TIME 130 | 131 | /* Define to 1 if your declares `struct tm'. */ 132 | #undef TM_IN_SYS_TIME 133 | 134 | /* Version number of package */ 135 | #undef VERSION 136 | 137 | /* Define to `unsigned int' if does not define. */ 138 | #undef size_t 139 | -------------------------------------------------------------------------------- /config.h.win: -------------------------------------------------------------------------------- 1 | /* config.h. Generated from config.h.in by configure. */ 2 | /* config.h.in. Generated from configure.ac by autoheader. */ 3 | 4 | /* #undef FD_SETSIZE */ 5 | /* #undef FD_SETSIZE */ 6 | #define FD_SETSIZE 1024 7 | 8 | /* #undef HAVE_ACCEPT4 */ 9 | //#define HAVE_ACCEPT4 1 10 | 11 | /* #undef HAVE_DAEMON */ 12 | /* #undef HAVE_DAEMON */ 13 | 14 | /* Define to 1 if you have the header file, and it defines `DIR'. 15 | */ 16 | #define HAVE_DIRENT_H 1 17 | 18 | /* #undef HAVE_EPOLL */ 19 | //#define HAVE_EPOLL 1 20 | 21 | /* #undef HAVE_GETADDRINFO */ 22 | #define HAVE_GETADDRINFO 1 23 | 24 | /* Define to 1 if you have the header file. */ 25 | #define HAVE_INTTYPES_H 1 26 | 27 | /* #undef HAVE_KQUEUE */ 28 | /* #undef HAVE_KQUEUE */ 29 | 30 | /* Define to 1 if you have the `crypto' library (-lcrypto). */ 31 | #define HAVE_LIBCRYPTO 1 32 | 33 | /* Define to 1 if you have the `GeoIP' library (-lGeoIP). */ 34 | //#define HAVE_LIBGEOIP 1 35 | 36 | /* Define to 1 if you have the `nsl' library (-lnsl). */ 37 | #define HAVE_LIBNSL 1 38 | 39 | /* Define to 1 if you have the `resolv' library (-lresolv). */ 40 | #define HAVE_LIBRESOLV 1 41 | 42 | /* Define to 1 if you have the `socket' library (-lsocket). */ 43 | /* #undef HAVE_LIBSOCKET */ 44 | 45 | /* Define to 1 if you have the `ssl' library (-lssl). */ 46 | //#define HAVE_LIBSSL 1 47 | 48 | /* Define to 1 if you have the header file. */ 49 | #define HAVE_MEMORY_H 1 50 | 51 | /* Define to 1 if you have the header file, and it defines `DIR'. */ 52 | /* #undef HAVE_NDIR_H */ 53 | 54 | /* #undef HAVE_POLL */ 55 | //#define HAVE_POLL 1 56 | 57 | /* Define to 1 if you have the header file. */ 58 | #define HAVE_STDINT_H 1 59 | 60 | /* Define to 1 if you have the header file. */ 61 | #define HAVE_STDLIB_H 1 62 | 63 | /* Define to 1 if you have the header file. */ 64 | #define HAVE_STRINGS_H 1 65 | 66 | /* Define to 1 if you have the header file. */ 67 | #define HAVE_STRING_H 1 68 | 69 | /* Define to 1 if you have the header file, and it defines `DIR'. 70 | */ 71 | /* #undef HAVE_SYS_DIR_H */ 72 | 73 | /* Define to 1 if you have the header file, and it defines `DIR'. 74 | */ 75 | /* #undef HAVE_SYS_NDIR_H */ 76 | 77 | /* Define to 1 if you have the header file. */ 78 | #define HAVE_SYS_SELECT_H 1 79 | 80 | /* Define to 1 if you have the header file. */ 81 | #define HAVE_SYS_STAT_H 1 82 | 83 | /* Define to 1 if you have the header file. */ 84 | #define HAVE_SYS_TYPES_H 1 85 | 86 | /* Define to 1 if you have the header file. */ 87 | #define HAVE_UNISTD_H 1 88 | 89 | /* Define to 1 if `major', `minor', and `makedev' are declared in . 90 | */ 91 | /* #undef MAJOR_IN_MKDEV */ 92 | 93 | /* Define to 1 if `major', `minor', and `makedev' are declared in 94 | . */ 95 | /* #undef MAJOR_IN_SYSMACROS */ 96 | 97 | /* Name of package */ 98 | #define PACKAGE "pen" 99 | 100 | /* Define to the address where bug reports for this package should be sent. */ 101 | #define PACKAGE_BUGREPORT "" 102 | 103 | /* Define to the full name of this package. */ 104 | #define PACKAGE_NAME "pen" 105 | 106 | /* Define to the full name and version of this package. */ 107 | #define PACKAGE_STRING "pen 0.27.0" 108 | 109 | /* Define to the one symbol short name of this package. */ 110 | #define PACKAGE_TARNAME "pen" 111 | 112 | /* Define to the home page for this package. */ 113 | #define PACKAGE_URL "" 114 | 115 | /* Define to the version of this package. */ 116 | #define PACKAGE_VERSION "0.27.0" 117 | 118 | /* Define to 1 if you have the ANSI C header files. */ 119 | #define STDC_HEADERS 1 120 | 121 | /* Define to 1 if you can safely include both and . */ 122 | #define TIME_WITH_SYS_TIME 1 123 | 124 | /* Define to 1 if your declares `struct tm'. */ 125 | /* #undef TM_IN_SYS_TIME */ 126 | 127 | /* Version number of package */ 128 | #define VERSION "0.28.0" 129 | 130 | /* Define to `unsigned int' if does not define. */ 131 | /* #undef size_t */ 132 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl Process this file with autoconf to produce a configure script. 2 | AC_INIT([pen],[0.35.0]) 3 | AC_CONFIG_SRCDIR([pen.c]) 4 | AC_CONFIG_HEADERS(config.h) 5 | AM_INIT_AUTOMAKE 6 | 7 | dnl Checks for programs. 8 | AC_PROG_CC 9 | AC_PROG_INSTALL 10 | AC_PROG_LN_S 11 | AC_ISC_POSIX 12 | 13 | dnl Checks for libraries. 14 | AC_CHECK_LIB(socket, main) 15 | AC_CHECK_LIB(nsl, main) 16 | dnl Next line is for Solaris 17 | AC_CHECK_LIB(resolv, inet_aton) 18 | 19 | dnl Checks for header files. 20 | AC_HEADER_DIRENT 21 | AC_HEADER_STDC 22 | AC_HEADER_MAJOR 23 | 24 | dnl Checks for typedefs, structures, and compiler characteristics. 25 | AC_TYPE_SIZE_T 26 | AC_HEADER_TIME 27 | AC_STRUCT_TM 28 | 29 | if test "$GCC" = "yes"; then 30 | CFLAGS="-Wall $CFLAGS" 31 | fi 32 | 33 | dnl Checks for library functions. 34 | dnl AC_CHECK_FUNC(inet_aton, AC_DEFINE(HAVE_INET_ATON, 1, [#undef HAVE_INET_ATON])) 35 | AC_CHECK_FUNC(getaddrinfo, AC_DEFINE(HAVE_GETADDRINFO, 1, [#undef HAVE_GETADDRINFO])) 36 | AC_CHECK_FUNC(accept4, AC_DEFINE(HAVE_ACCEPT4, 1, [#undef HAVE_ACCEPT4])) 37 | 38 | dnl Check things for pen 39 | 40 | AC_CHECK_HEADERS([sys/select.h]) 41 | 42 | AC_ARG_ENABLE(profiling, 43 | [ --enable-profiling enable profiling], 44 | [ if test "$withval" != "no"; then 45 | CFLAGS="$CFLAGS -pg" 46 | fi ]) 47 | 48 | AC_ARG_ENABLE(debugging, AS_HELP_STRING([--enable-debugging], [enable debugging messages])) 49 | AS_IF([test "$enable_debugging" != "no"], [CFLAGS="$CFLAGS -DDEBUGGING"]) 50 | 51 | AC_ARG_WITH(daemon, 52 | [ --with-daemon use daemon() if available], 53 | [ if test "$withval" != "no"; then 54 | AC_CHECK_FUNC(daemon, 55 | AC_DEFINE([HAVE_DAEMON], 1, [#undef HAVE_DAEMON])) 56 | fi ]) 57 | 58 | AC_ARG_WITH(poll, AS_HELP_STRING([--with-poll], [use poll() if available])) 59 | AS_IF([test "$with_poll" != "no"], 60 | AC_CHECK_FUNC(poll, 61 | AC_DEFINE([HAVE_POLL], 1, [#undef HAVE_POLL]))) 62 | 63 | AC_ARG_WITH(epoll, AS_HELP_STRING([--with-epoll], [use epoll if available])) 64 | AS_IF([test "$with_epoll" != "no"], 65 | AC_CHECK_FUNC(epoll_create1, 66 | AC_DEFINE([HAVE_EPOLL], 1, [#undef HAVE_EPOLL]))) 67 | 68 | AC_ARG_WITH(kqueue, AS_HELP_STRING([--with-kqueue], [use kqueue() if available])) 69 | AS_IF([test "$with_kqueue" != "no"], 70 | AC_CHECK_FUNC(kqueue, 71 | AC_DEFINE([HAVE_KQUEUE], 1, [#undef HAVE_KQUEUE]))) 72 | 73 | AC_ARG_WITH(fd_setsize, 74 | [ --with-fd_setsize=N set FD_SETSIZE to N (see INSTALL)], 75 | [ if test "$withval" != "no"; then 76 | AC_DEFINE_UNQUOTED([FD_SETSIZE], $withval, [#undef FD_SETSIZE]) 77 | fi ]) 78 | 79 | AC_ARG_WITH(ssl, AS_HELP_STRING([--with-ssl], [use SSL (default /usr/local/ssl)])) 80 | AS_IF([test "$with_ssl" != "no"], 81 | AC_CHECK_LIB(crypto, main) 82 | AC_CHECK_LIB(ssl, main)) 83 | 84 | dnl Some people think the EC stuff is patented and needs to be stripped out 85 | AC_CHECK_FUNC(EC_KEY_new_by_curve_name, 86 | AC_DEFINE([HAVE_EC_KEY], 1, [#undef HAVE_EC_KEY])) 87 | 88 | AC_ARG_WITH(geoip, AS_HELP_STRING([--with-geoip], [use libgeoip])) 89 | AS_IF([test "$with_geoip" != "no"], 90 | AC_CHECK_LIB([GeoIP], [GeoIP_country_code_by_addr])) 91 | 92 | AC_ARG_WITH(dsr, AS_HELP_STRING([--with-dsr], [enable direct server return])) 93 | AS_IF([test "$with_dsr" != "no"], 94 | [ AC_CHECK_HEADERS([linux/if_packet.h]) 95 | AC_CHECK_HEADERS([net/netmap_user.h])]) 96 | 97 | docdir='${prefix}/doc/pen' 98 | AC_ARG_WITH(docdir, 99 | [ --with-docdir=DIR install docs in DIR [[PREFIX/doc/pen]]], 100 | [ if test "$withval" != "yes" && test "$withval" != "no"; then 101 | docdir=$withval 102 | fi ]) 103 | AC_SUBST(docdir) 104 | 105 | AC_CONFIG_FILES([Makefile]) 106 | AC_OUTPUT 107 | 108 | -------------------------------------------------------------------------------- /conn.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #ifndef WINDOWS 5 | #include 6 | #endif 7 | #include 8 | #include "conn.h" 9 | #include "client.h" 10 | #include "diag.h" 11 | #include "dlist.h" 12 | #include "event.h" 13 | #include "idlers.h" 14 | #include "memory.h" 15 | #include "pen.h" 16 | #include "server.h" 17 | #include "settings.h" 18 | 19 | int idle_timeout = 0; /* never time out */ 20 | int pending_list = -1; /* pending connections */ 21 | int pending_queue = 0; /* number of pending connections */ 22 | int pending_max = 100; /* max number of pending connections */ 23 | connection *conns; 24 | int connections_max = 0; 25 | int connections_used = 0; 26 | int connections_last = 0; 27 | int tracking_time = TRACKING_TIME; 28 | static int fd2conn_max = 0; 29 | static int *fd2conn; 30 | 31 | /* store_conn does fd2conn_set(fd, conn) */ 32 | /* close_conn does fd2conn_set(fd, -1) */ 33 | void fd2conn_set(int fd, int conn) 34 | { 35 | DEBUG(3, "fd2conn_set(fd=%d, conn=%d)", fd, conn); 36 | if (fd < 0) error("fd2conn_set(fd = %d, conn = %d)", fd, conn); 37 | if (fd >= fd2conn_max) { 38 | int i, new_max = fd+10000; 39 | DEBUG(2, "expanding fd2conn to %d bytes", new_max); 40 | fd2conn = pen_realloc(fd2conn, new_max * sizeof *fd2conn); 41 | for (i = fd2conn_max; i < new_max; i++) fd2conn[i] = -1; 42 | fd2conn_max = new_max; 43 | } 44 | fd2conn[fd] = conn; 45 | } 46 | 47 | int fd2conn_get(int fd) 48 | { 49 | if (fd < 0 || fd >= fd2conn_max) return -1; 50 | return fd2conn[fd]; 51 | } 52 | 53 | int closing_time(int conn) 54 | { 55 | int closed = conns[conn].state & CS_CLOSED; 56 | 57 | if (closed == CS_CLOSED) return 1; 58 | if (conns[conn].downn + conns[conn].upn == 0) { 59 | return closed & tcp_fastclose; 60 | } 61 | return 0; 62 | } 63 | 64 | #ifdef HAVE_LIBSSL 65 | int store_conn(int downfd, SSL *ssl, int client) 66 | #else 67 | int store_conn(int downfd, int client) 68 | #endif 69 | { 70 | int i; 71 | 72 | i = connections_last; 73 | do { 74 | if ((conns[i].state == CS_UNUSED) || (conns[i].state & CS_HALFDEAD)) break; 75 | if (udp) conns[i].state |= CS_HALFDEAD; 76 | i++; 77 | if (i >= connections_max) i = 0; 78 | } while (i != connections_last); 79 | 80 | /* For TCP, we have either CS_UNUSED or something we can't use */ 81 | /* For UDP, we have either CS_UNUSED or CS_HALFDEAD */ 82 | if (conns[i].state & CS_HALFDEAD) { 83 | DEBUG(2, "Recycling halfdead connection %d", i); 84 | close_conn(i); 85 | } 86 | /* And now UDP is guaranteed to be CS_UNUSED */ 87 | 88 | if (conns[i].state == CS_UNUSED) { 89 | connections_last = i; 90 | connections_used++; 91 | DEBUG(2, "incrementing connections_used to %d for connection %d", 92 | connections_used, i); 93 | conns[i].upfd = -1; 94 | conns[i].downfd = downfd; 95 | if (downfd != -1) fd2conn_set(downfd, i); 96 | #ifdef HAVE_LIBSSL 97 | conns[i].ssl = ssl; 98 | #endif 99 | conns[i].client = client; 100 | conns[i].initial = NO_SERVER; 101 | conns[i].server = NO_SERVER; 102 | conns[i].srx = conns[i].ssx = 0; 103 | conns[i].crx = conns[i].csx = 0; 104 | } else { 105 | i = -1; 106 | if (debuglevel) 107 | debug("Connection table full (%d slots), can't store connection.\n" 108 | "Try restarting with -x %d", 109 | connections_max, 2*connections_max); 110 | if (downfd != -1) close(downfd); 111 | } 112 | DEBUG(2, "store_conn: conn = %d, downfd = %d, connections_used = %d", \ 113 | i, downfd, connections_used); 114 | return i; 115 | } 116 | 117 | int idler(int conn) 118 | { 119 | return (conns[conn].state & CS_CONNECTED) && (conns[conn].client == -1); 120 | } 121 | 122 | void close_conn(int i) 123 | { 124 | int index = conns[i].server; 125 | 126 | /* unfinished connections have server == NO_SERVER */ 127 | if (index != NO_SERVER) { 128 | servers[index].c -= 1; 129 | if (servers[index].c < 0) servers[index].c = 0; 130 | } 131 | 132 | if (conns[i].upfd != -1 && conns[i].upfd != listenfd) { 133 | event_delete(conns[i].upfd); 134 | close(conns[i].upfd); 135 | fd2conn_set(conns[i].upfd, -1); 136 | } 137 | #ifdef HAVE_LIBSSL 138 | if (conns[i].ssl) { 139 | int n = SSL_shutdown(conns[i].ssl); 140 | DEBUG(3, "First SSL_shutdown(%d) returns %d", conns[i].ssl, n); 141 | if (n == 0) { 142 | n = SSL_shutdown(conns[i].ssl); 143 | DEBUG(3, "Second SSL_shutdown(%d) returns %d", conns[i].ssl, n); 144 | } 145 | if (n == -1) { 146 | n = SSL_get_error(conns[i].ssl, n); 147 | DEBUG(3, "%s", ERR_error_string(SSL_get_error(conns[i].ssl, n), NULL)); 148 | ERR_print_errors_fp(stderr); 149 | } 150 | SSL_free(conns[i].ssl); 151 | conns[i].ssl = 0; 152 | } 153 | #endif 154 | if (conns[i].downfd != -1 && conns[i].downfd != listenfd) { 155 | event_delete(conns[i].downfd); 156 | close(conns[i].downfd); 157 | fd2conn_set(conns[i].downfd, -1); 158 | } 159 | if (idler(i)) idlers--; 160 | conns[i].upfd = conns[i].downfd = -1; 161 | if (conns[i].downn) { 162 | free(conns[i].downb); 163 | conns[i].downn=0; 164 | } 165 | if (conns[i].upn) { 166 | free(conns[i].upb); 167 | conns[i].upn=0; 168 | } 169 | connections_used--; 170 | DEBUG(2, "decrementing connections_used to %d for connection %d", 171 | connections_used, i); 172 | if (connections_used < 0) { 173 | debug("connections_used = %d. Resetting.", connections_used); 174 | connections_used = 0; 175 | } 176 | if (conns[i].state & CS_IN_PROGRESS) { 177 | pending_list = dlist_remove(conns[i].pend); 178 | pending_queue--; 179 | } 180 | conns[i].state = CS_UNUSED; 181 | DEBUG(2, "close_conn: Closing connection %d to server %d; connections_used = %d\n" \ 182 | "\tRead %ld from client, wrote %ld to server\n" \ 183 | "\tRead %ld from server, wrote %ld to client", \ 184 | i, index, connections_used, \ 185 | conns[i].crx, conns[i].ssx, \ 186 | conns[i].srx, conns[i].csx); 187 | DEBUG(3, "Aggregate for server %d: sx=%d, rx=%d", 188 | conns[i].server, servers[conns[i].server].sx, servers[conns[i].server].rx); 189 | } 190 | 191 | void expand_conntable(size_t size) 192 | { 193 | int i; 194 | DEBUG(1, "expand_conntable(%d)", size); 195 | if (size < connections_max) return; /* nothing to do */ 196 | conns = pen_realloc(conns, size*sizeof *conns); 197 | memset(&conns[connections_max], 0, (size-connections_max)*sizeof *conns); 198 | for (i = connections_max; i < size; i++) { 199 | conns[i].upfd = conns[i].downfd = -1; 200 | } 201 | connections_max = size; 202 | } 203 | 204 | -------------------------------------------------------------------------------- /conn.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef HAVE_LIBSSL 3 | #include 4 | #include 5 | #endif 6 | 7 | #include 8 | 9 | #define CONNECTIONS_MAX 500 /* max simultaneous connections */ 10 | 11 | /* Connection states, as used in struct conn */ 12 | #define CS_UNUSED (0) /* not connected, unused slot */ 13 | #define CS_IN_PROGRESS (1) /* connection in progress */ 14 | #define CS_CONNECTED (2) /* successfully connected */ 15 | #define CS_CLOSED_UP (4) /* we read eof from upfd */ 16 | #define CS_CLOSED_DOWN (8) /* we read eof from downfd */ 17 | #define CS_CLOSED (CS_CLOSED_UP | CS_CLOSED_DOWN) 18 | #define CS_HALFDEAD (16) /* has not seen recent traffic */ 19 | #define CS_WAIT_PEEK (32) /* waiting for client's first frame */ 20 | 21 | typedef struct { 22 | int state; /* as per above */ 23 | time_t t; /* time of connection attempt */ 24 | int downfd, upfd; 25 | unsigned char *downb, *downbptr, *upb, *upbptr; 26 | int downn, upn; /* pending bytes */ 27 | unsigned long ssx, srx; /* server total sent, received */ 28 | unsigned long csx, crx; /* client total sent, received */ 29 | int client; /* client index */ 30 | int initial; /* first server tried */ 31 | int server; /* server index */ 32 | int pend; /* node in pending_list */ 33 | #ifdef HAVE_LIBSSL 34 | SSL *ssl; 35 | time_t reneg; /* last time client requested renegotiation */ 36 | #endif 37 | } connection; 38 | 39 | extern connection *conns; 40 | extern int idle_timeout; 41 | extern int pending_list; 42 | extern int pending_queue; 43 | extern int pending_max; 44 | extern int connections_max; 45 | extern int connections_used; 46 | extern int connections_last; 47 | extern int tracking_time; 48 | 49 | extern void fd2conn_set(int, int); 50 | extern int fd2conn_get(int); 51 | extern int closing_time(int); 52 | #ifdef HAVE_LIBSSL 53 | int store_conn(int, SSL *, int); 54 | #else 55 | int store_conn(int, int); 56 | #endif 57 | extern int idler(int); 58 | extern void close_conn(int); 59 | extern void expand_conntable(size_t); 60 | -------------------------------------------------------------------------------- /diag.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #ifndef WINDOWS 6 | #include 7 | #else 8 | #include "windows.h" 9 | #endif 10 | #include "settings.h" 11 | 12 | int debuglevel; 13 | 14 | #ifdef WINDOWS 15 | static FILE *syslogfp; 16 | 17 | static void openlog(const char *ident, int option, int facility) 18 | { 19 | syslogfp = fopen("syslog.txt", "a"); 20 | } 21 | 22 | static void syslog(int priority, const char *format, ...) 23 | { 24 | va_list ap; 25 | va_start(ap, format); 26 | if (syslogfp) { 27 | vfprintf(syslogfp, format, ap); 28 | } 29 | va_end(ap); 30 | } 31 | 32 | static void closelog(void) 33 | { 34 | fclose(syslogfp); 35 | } 36 | #endif 37 | 38 | void debug(char *fmt, ...) 39 | { 40 | time_t now; 41 | struct tm *nowtm; 42 | char nowstr[80]; 43 | char b[4096]; 44 | va_list ap; 45 | va_start(ap, fmt); 46 | vsnprintf(b, sizeof b, fmt, ap); 47 | now=time(NULL); 48 | nowtm = localtime(&now); 49 | strftime(nowstr, sizeof(nowstr), "%Y-%m-%d %H:%M:%S", nowtm); 50 | if (foreground) { 51 | fprintf(stderr, "%s: %s\n", nowstr, b); 52 | } else { 53 | openlog("pen", LOG_CONS, LOG_USER); 54 | syslog(LOG_DEBUG, "%s\n", b); 55 | closelog(); 56 | } 57 | va_end(ap); 58 | } 59 | 60 | void error(char *fmt, ...) 61 | { 62 | char b[4096]; 63 | va_list ap; 64 | va_start(ap, fmt); 65 | vsnprintf(b, sizeof b, fmt, ap); 66 | fprintf(stderr, "%s\n", b); 67 | if (!foreground) { 68 | openlog("pen", LOG_CONS, LOG_USER); 69 | syslog(LOG_ERR, "%s\n", b); 70 | closelog(); 71 | } 72 | va_end(ap); 73 | if (abort_on_error) abort(); 74 | else exit(EXIT_FAILURE); 75 | } 76 | 77 | -------------------------------------------------------------------------------- /diag.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef DEBUGGING 4 | #define DEBUG(lvl, ...) \ 5 | if (debuglevel >= lvl) { \ 6 | debug(__VA_ARGS__); \ 7 | } 8 | #define DEBUG_ERRNO(lvl, ...) \ 9 | if (debuglevel >= lvl) { \ 10 | err = socket_errno; \ 11 | debug(__VA_ARGS__); \ 12 | } 13 | #define SPAM \ 14 | if (debuglevel >= 2) \ 15 | debug("File %s, line %d, function %s", \ 16 | __FILE__, __LINE__, __func__); 17 | #else 18 | #define DEBUG(lvl, ...) 19 | #define DEBUG_ERRNO(lvl, ...) 20 | #define SPAM 21 | #endif 22 | 23 | #ifdef WINDOWS 24 | #define socket_errno WSAGetLastError() 25 | #else 26 | #define socket_errno errno 27 | #endif 28 | 29 | extern int debuglevel; 30 | extern void debug(char *, ...); 31 | extern void error(char *, ...); 32 | -------------------------------------------------------------------------------- /dlist.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "memory.h" 3 | 4 | /* circular doubly linked lists, used for pending and closing connections */ 5 | 6 | struct node { 7 | int value; /* index into connection table */ 8 | int prev; /* back pointer */ 9 | int next; /* forward pointer */ 10 | }; 11 | 12 | static struct node *nodes; 13 | static int nodes_max; 14 | 15 | /* This finishes in almost-constant time if there are plenty of free nodes */ 16 | static int alloc_node(void) 17 | { 18 | static int last_node = 0; 19 | int start = last_node; 20 | do { 21 | if (nodes[last_node].value == -1) return last_node; 22 | last_node = (last_node+1)%nodes_max; 23 | } while (last_node != start); 24 | return -1; /* all nodes used */ 25 | } 26 | 27 | /* Insert value into a new node. Return the resulting list. */ 28 | int dlist_insert(int list, int value) 29 | { 30 | int new_node = alloc_node(); 31 | if (new_node == -1) return -1; 32 | if (list == -1) { /* empty */ 33 | nodes[new_node].prev = new_node; 34 | nodes[new_node].next = new_node; 35 | 36 | } else { 37 | int prev = nodes[list].prev; 38 | nodes[prev].next = new_node; 39 | nodes[new_node].prev = prev; 40 | nodes[new_node].next = list; 41 | nodes[list].prev = new_node; 42 | } 43 | nodes[new_node].value = value; 44 | return new_node; 45 | } 46 | 47 | /* Remove a node from its list. Return the resulting list. */ 48 | int dlist_remove(int node) 49 | { 50 | int prev, next; 51 | if (node == -1) return -1; 52 | nodes[node].value = -1; /* mark as unused */ 53 | if (nodes[node].next == node) { /* last node */ 54 | return -1; /* empty list */ 55 | } 56 | prev = nodes[node].prev; 57 | next = nodes[node].next; 58 | nodes[prev].next = next; 59 | nodes[next].prev = prev; 60 | return next; 61 | } 62 | 63 | /* Free an entire list by marking all nodes as unused. */ 64 | void dlist_free(int list) 65 | { 66 | int start = list; 67 | if (list == -1) return; 68 | do { 69 | nodes[list].value = -1; 70 | list = (list+1)%nodes_max; 71 | } while (list != start); 72 | } 73 | 74 | /* Return next node in the list. Valid even for freed nodes. */ 75 | int dlist_next(int node) 76 | { 77 | return nodes[node].next; 78 | } 79 | 80 | int dlist_value(int node) 81 | { 82 | return nodes[node].value; 83 | } 84 | 85 | /* Allocate enough nodes for all the doubly linked lists we'll ever need. */ 86 | void dlist_init(int size) 87 | { 88 | int i; 89 | nodes_max = size; 90 | nodes = pen_malloc(nodes_max * sizeof *nodes); 91 | for (i = 0; i < size; i++) { 92 | nodes[i].value = -1; /* unused */ 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /dlist.h: -------------------------------------------------------------------------------- 1 | /* dlist.c */ 2 | extern int dlist_insert(int, int); 3 | extern int dlist_remove(int); 4 | extern void dlist_free(int); 5 | extern int dlist_next(int); 6 | extern int dlist_value(int); 7 | extern void dlist_init(int); 8 | -------------------------------------------------------------------------------- /dsr.h: -------------------------------------------------------------------------------- 1 | extern int dsr_init(char *, char *); 2 | extern void dsr_frame(int); 3 | extern void dsr_arp(int); 4 | extern int tarpit_acl; 5 | -------------------------------------------------------------------------------- /epoll.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | //#include "pen.h" 7 | #include "conn.h" 8 | #include "diag.h" 9 | #include "event.h" 10 | #include "memory.h" 11 | #ifdef HAVE_EPOLL 12 | #include 13 | 14 | static int efd; 15 | static struct epoll_event *epoll_ev; 16 | static int epoll_count, maxevents; 17 | static int pindex; 18 | 19 | static void epoll_event_ctl(int fd, int events, int op) 20 | { 21 | int n, epevents = 0; 22 | struct epoll_event ev; 23 | memset(&ev, 0, sizeof(struct epoll_event)); 24 | DEBUG(2, "epoll_event_ctl(fd=%d, events=%d, op=%d)", fd, events, op); 25 | if (events & EVENT_READ) epevents |= EPOLLIN; 26 | if (events & EVENT_WRITE) epevents |= EPOLLOUT; 27 | ev.events = epevents; 28 | ev.data.fd = fd; 29 | n = epoll_ctl(efd, op, fd, &ev); 30 | if (n == -1) { 31 | error("epoll_ctl: %s", strerror(errno)); 32 | } 33 | } 34 | 35 | static void epoll_event_add(int fd, int events) 36 | { 37 | DEBUG(2, "epoll_event_add(fd=%d, events=%d)", fd, events); 38 | epoll_event_ctl(fd, events, EPOLL_CTL_ADD); 39 | } 40 | 41 | static void epoll_event_arm(int fd, int events) 42 | { 43 | DEBUG(2, "epoll_event_arm(fd=%d, events=%d)", fd, events); 44 | epoll_event_ctl(fd, events, EPOLL_CTL_MOD); 45 | } 46 | 47 | /* We don't need to do anything here, because this function is only called 48 | when we are about to close the socket. 49 | */ 50 | static void epoll_event_delete(int fd) 51 | { 52 | DEBUG(2, "epoll_event_delete(fd=%d)", fd); 53 | } 54 | 55 | static void epoll_event_wait(void) 56 | { 57 | DEBUG(2, "epoll_event_wait()"); 58 | pindex = -1; 59 | epoll_count = epoll_wait(efd, epoll_ev, maxevents, 1000*timeout); 60 | DEBUG(2, "epoll_wait returns %d", epoll_count); 61 | if (epoll_count == -1 && errno != EINTR) { 62 | error("Error on epoll_wait: %s", strerror(errno)); 63 | } 64 | } 65 | 66 | static int epoll_event_fd(int *revents) 67 | { 68 | int events = 0; 69 | DEBUG(2, "epoll_event_fd(revents=%p)", revents); 70 | pindex++; 71 | if (pindex >= epoll_count) return -1; 72 | DEBUG(3, "\tepoll_ev[%d] = {revents=%d, data.fd=%d}", pindex, epoll_ev[pindex].events, epoll_ev[pindex].data.fd); 73 | if (epoll_ev[pindex].events & EPOLLIN) events |= EVENT_READ; 74 | if (epoll_ev[pindex].events & EPOLLOUT) events |= EVENT_WRITE; 75 | if (epoll_ev[pindex].events & EPOLLERR) events |= EVENT_ERR; 76 | if (epoll_ev[pindex].events & EPOLLHUP) events |= EVENT_ERR; 77 | if (events == 0) DEBUG(2, "events for fd %d = %d", epoll_ev[pindex].data.fd, epoll_ev[pindex].events); 78 | *revents = events; 79 | return epoll_ev[pindex].data.fd; 80 | } 81 | 82 | void epoll_init(void) 83 | { 84 | efd = epoll_create1(0); 85 | DEBUG(2, "epoll_create1 returns %d", efd); 86 | if (efd == -1) { 87 | debug("epoll_create1: %s", strerror(errno)); 88 | error("Error creating epoll fd"); 89 | } 90 | maxevents = connections_max*2+2; 91 | epoll_ev = pen_malloc(maxevents*sizeof *epoll_ev); 92 | event_add = epoll_event_add; 93 | event_arm = epoll_event_arm; 94 | event_delete = epoll_event_delete; 95 | event_wait = epoll_event_wait; 96 | event_fd = epoll_event_fd; 97 | } 98 | #else 99 | void epoll_init(void) 100 | { 101 | debug("You don't have epoll"); 102 | exit(EXIT_FAILURE); 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /event.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "event.h" 3 | #include "pen_epoll.h" 4 | #include "pen_kqueue.h" 5 | #include "pen_poll.h" 6 | #include "pen_select.h" 7 | 8 | int timeout = TIMEOUT; 9 | 10 | void (*event_add)(int, int); 11 | void (*event_arm)(int, int); 12 | void (*event_delete)(int); 13 | void (*event_wait)(void); 14 | int (*event_fd)(int *); 15 | #if defined HAVE_KQUEUE 16 | void (*event_init)(void) = kqueue_init; 17 | #elif defined HAVE_EPOLL 18 | void (*event_init)(void) = epoll_init; 19 | #elif defined HAVE_POLL 20 | void (*event_init)(void) = poll_init; 21 | #else 22 | void (*event_init)(void) = select_init; 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /event.h: -------------------------------------------------------------------------------- 1 | #define EVENT_READ (0x10000) 2 | #define EVENT_WRITE (0x20000) 3 | #define EVENT_ERR (0x40000) 4 | 5 | #define TIMEOUT 3 /* default timeout for non reachable hosts */ 6 | 7 | extern int timeout; 8 | 9 | extern void (*event_init)(void); 10 | extern void (*event_add)(int, int); 11 | extern void (*event_arm)(int, int); 12 | extern void (*event_delete)(int); 13 | extern void (*event_wait)(void); 14 | extern int (*event_fd)(int *); 15 | 16 | -------------------------------------------------------------------------------- /idlers.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "conn.h" 3 | #include "diag.h" 4 | #include "idlers.h" 5 | #include "server.h" 6 | 7 | int idlers = 0, idlers_wanted = 0; 8 | 9 | void close_idlers(int n) 10 | { 11 | int conn; 12 | 13 | DEBUG(2, "close_idlers(%d)", n); 14 | for (conn = 0; n > 0 && conn < connections_max; conn++) { 15 | if (idler(conn)) { 16 | DEBUG(3, "Closing idling connection %d", conn); 17 | close_conn(conn); 18 | n--; 19 | } 20 | } 21 | } 22 | 23 | int add_idler(void) 24 | { 25 | #ifdef HAVE_LIBSSL 26 | int conn = store_conn(-1, NULL, -1); 27 | #else 28 | int conn = store_conn(-1, -1); 29 | #endif 30 | if (conn == -1) return 0; 31 | conns[conn].initial = server_by_roundrobin(); 32 | if (conns[conn].initial == NO_SERVER) { 33 | close_conn(conn); 34 | return 0; 35 | } 36 | if (!try_server(conns[conn].initial, conn)) { 37 | if (!failover_server(conn)) { 38 | close_conn(conn); 39 | return 0; 40 | } 41 | } 42 | idlers++; 43 | return 1; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /idlers.h: -------------------------------------------------------------------------------- 1 | extern int idlers, idlers_wanted; 2 | 3 | extern void close_idlers(int); 4 | extern int add_idler(void); 5 | -------------------------------------------------------------------------------- /kqueue.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "conn.h" 7 | #include "diag.h" 8 | #include "event.h" 9 | #include "memory.h" 10 | #ifdef HAVE_KQUEUE 11 | #include 12 | #include 13 | #include 14 | 15 | static int kq; 16 | static struct kevent *kev, *kev_out; 17 | static int nfds, maxevents; 18 | static int count; 19 | static int pindex; 20 | 21 | static void kqueue_event_add(int fd, int events) 22 | { 23 | DEBUG(2, "kqueue_event_add(fd=%d, events=%d)", fd, events); 24 | if (events & EVENT_READ) { 25 | EV_SET(&kev[nfds], fd, EVFILT_READ, EV_ADD|EV_ENABLE, 0, 0, 0); 26 | } else { 27 | EV_SET(&kev[nfds], fd, EVFILT_READ, EV_ADD|EV_DISABLE, 0, 0, 0); 28 | } 29 | nfds++; 30 | if (events & EVENT_WRITE) { 31 | EV_SET(&kev[nfds], fd, EVFILT_WRITE, EV_ADD|EV_ENABLE, 0, 0, 0); 32 | } else { 33 | EV_SET(&kev[nfds], fd, EVFILT_WRITE, EV_ADD|EV_DISABLE, 0, 0, 0); 34 | } 35 | nfds++; 36 | } 37 | 38 | static void kqueue_event_arm(int fd, int events) 39 | { 40 | DEBUG(2, "kqueue_event_arm(fd=%d, events=%d)", fd, events); 41 | if (events & EVENT_READ) { 42 | EV_SET(&kev[nfds], fd, EVFILT_READ, EV_ENABLE, 0, 0, 0); 43 | } else { 44 | EV_SET(&kev[nfds], fd, EVFILT_READ, EV_DISABLE, 0, 0, 0); 45 | } 46 | nfds++; 47 | if (events & EVENT_WRITE) { 48 | EV_SET(&kev[nfds], fd, EVFILT_WRITE, EV_ENABLE, 0, 0, 0); 49 | } else { 50 | EV_SET(&kev[nfds], fd, EVFILT_WRITE, EV_DISABLE, 0, 0, 0); 51 | } 52 | nfds++; 53 | } 54 | 55 | /* The only time we call event_delete is when we are about to close the fd, 56 | so we can save this operation since the fd will be deleted automatically. 57 | */ 58 | static void kqueue_event_delete(int fd) 59 | { 60 | DEBUG(2, "kqueue_event_delete(fd=%d)", fd); 61 | ; 62 | } 63 | 64 | static void kqueue_event_wait(void) 65 | { 66 | struct timespec tv; 67 | DEBUG(2, "kqueue_event_wait()"); 68 | tv.tv_sec = timeout; 69 | tv.tv_nsec = 0; 70 | count = kevent(kq, kev, nfds, kev_out, maxevents, &tv); 71 | DEBUG(2, "kevent returns %d", count); 72 | if (count < 0 && errno != EINTR) { 73 | error("Error on kevent: %s", strerror(errno)); 74 | } 75 | pindex = -1; 76 | nfds = 0; 77 | } 78 | 79 | static int kqueue_event_fd(int *revents) 80 | { 81 | int events = 0; 82 | DEBUG(2, "kqueue_event_fd(revents=%p)", revents); 83 | pindex++; 84 | if (pindex >= count) return -1; 85 | DEBUG(3, "\tkev_out[%d] = {filter=%d, ident=%d}", pindex, kev_out[pindex].filter, kev_out[pindex].ident); 86 | if (kev_out[pindex].filter == EVFILT_READ) events |= EVENT_READ; 87 | if (kev_out[pindex].filter == EVFILT_WRITE) events |= EVENT_WRITE; 88 | *revents = events; 89 | return kev_out[pindex].ident; 90 | } 91 | 92 | void kqueue_init(void) 93 | { 94 | kq = kqueue(); 95 | if (kq == -1) { 96 | error("Error creating kernel queue: %s", strerror(errno)); 97 | } 98 | maxevents = connections_max*2+2; 99 | kev = pen_malloc(maxevents*sizeof *kev); 100 | kev_out = pen_malloc(maxevents*sizeof *kev_out); 101 | event_add = kqueue_event_add; 102 | event_arm = kqueue_event_arm; 103 | event_delete = kqueue_event_delete; 104 | event_wait = kqueue_event_wait; 105 | event_fd = kqueue_event_fd; 106 | nfds = 0; 107 | } 108 | #else 109 | void kqueue_init(void) 110 | { 111 | debug("You don't have kqueue"); 112 | exit(EXIT_FAILURE); 113 | } 114 | #endif 115 | -------------------------------------------------------------------------------- /memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "diag.h" 4 | 5 | void *pen_malloc(size_t n) 6 | { 7 | void *q = malloc(n); 8 | if (!q) error("Can't malloc %ld bytes", (long)n); 9 | return q; 10 | } 11 | 12 | void *pen_calloc(size_t n, size_t s) 13 | { 14 | void *q = calloc(n, s); 15 | if (!q) error("Can't calloc %ld bytes", (long)n*s); 16 | return q; 17 | } 18 | 19 | void *pen_realloc(void *p, size_t n) 20 | { 21 | void *q = realloc(p, n); 22 | if (!q) error("Can't realloc %ld bytes", (long)n); 23 | return q; 24 | } 25 | 26 | char *pen_strdup(const char *p) 27 | { 28 | size_t len = strlen(p); 29 | char *b = pen_malloc(len+1); 30 | memcpy(b, p, len); 31 | b[len] = '\0'; 32 | return b; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /memory.h: -------------------------------------------------------------------------------- 1 | extern void *pen_malloc(size_t); 2 | extern void *pen_calloc(size_t, size_t); 3 | extern void *pen_realloc(void *, size_t); 4 | extern char *pen_strdup(const char *); 5 | -------------------------------------------------------------------------------- /mergelogs.1: -------------------------------------------------------------------------------- 1 | .TH MERGELOGS 1 LOCAL 2 | 3 | .SH NAME 4 | mergelogs - merge and consolidate web server logs 5 | 6 | .SH SYNOPSIS 7 | .B mergelogs 8 | -p penlog [-c] [-d] [-j jitter] [-t seconds] server1:logfile1 [server2:logfile2 ...] 9 | 10 | .SH EXAMPLES 11 | mergelogs -p pen.log 10.0.0.1:access_log.1 10.0.0.2:access_log.2 12 | 13 | mergelogs -p pen.log 10.0.18.6:access_log-10.0.18.6 10.0.18.8:access_log-10.0.18.8 14 | 15 | .SH DESCRIPTION 16 | When pen is used to load balance web servers, the web server log file 17 | lists all accesses as coming from the host running pen. This makes it 18 | more difficult to analyze the log file. 19 | 20 | To solve this, pen creates its own log file, which contains the real 21 | client address, the time of the access, the target server address 22 | and the first few bytes of the requests. 23 | 24 | Mergelogs reads pen's log file and the log files of all load balanced 25 | web servers, compares each entry and creates a combined log file 26 | that looks as if the web server cluster were a single physical server. 27 | Client addresses are replaced with the real client addresses. 28 | 29 | In the event that no matching client address can be found in 30 | the pen log, the server address is used instead. This should never 31 | happen, and is meant as a debugging tool. A large number of these 32 | indicates that the server system date needs to be set, or that 33 | the jitter value is too small. 34 | 35 | You probably don't want to use this program. Penlog is a much more 36 | elegant and functional solution. 37 | 38 | .SH OPTIONS 39 | .TP 40 | -c 41 | Do not cache pen log entries. The use of this option is not recommended, 42 | as it will make mergelogs search the 43 | entire pen log for every line in the web server logs. 44 | .TP 45 | -d 46 | Debugging (repeat for more). 47 | .TP 48 | -p \fIpenlog\fR 49 | Log file from pen. 50 | .TP 51 | -j \fIjitter\fR 52 | Jitter in seconds (default 600). This is the maximum variation in time 53 | stamps in the pen and web server log files. A smaller value will result 54 | in a smaller pen log cache and faster processing, at the risk of 55 | missed entries. 56 | .TP 57 | -t \fIseconds\fR 58 | The difference in seconds between the time on the pen server and UTC. 59 | For example, this is 7200 (two hours) in Finland. 60 | .TP 61 | server:logfile 62 | Web server address and name of log file. 63 | 64 | .SH AUTHOR 65 | Copyright (C) 2001-2015 Ulric Eriksson, . 66 | 67 | .SH SEE ALSO 68 | pen(1), webresolve(1), penlog(1), penlogd(1) 69 | -------------------------------------------------------------------------------- /mergelogs.c: -------------------------------------------------------------------------------- 1 | /* 2 | mergelogs.c 3 | 4 | Copyright (C) 2001-2015 Ulric Eriksson 5 | 6 | This program is free software; you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation; either version 2, or (at your option) 9 | any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program; if not, write to the Free Software 18 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, 19 | MA 02111-1307, USA. 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include "diag.h" 31 | #include "memory.h" 32 | #include "settings.h" 33 | 34 | #define KEEP_MAX 100 /* how much to keep from the URI */ 35 | 36 | typedef struct { 37 | char *a; /* server name */ 38 | char *fn; /* log file name */ 39 | FILE *fp; /* file pointer, or NULL if eof */ 40 | time_t t; /* time stamp from last line */ 41 | char cli[1024]; /* client address */ 42 | char tim[1024]; /* time string */ 43 | char uri[1024]; /* uri */ 44 | char b1[1024], b2[1024], b3[1024]; /* misc text */ 45 | } server; 46 | 47 | static server *servers; 48 | static int nservers; 49 | 50 | static char *pfile; 51 | static int jitter = 600; /* 10 minutes */ 52 | static FILE *pfp; 53 | static int tz = 0; 54 | static int cache_penlog = 1; 55 | 56 | static char *months[] = { 57 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", 58 | "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 59 | }; 60 | 61 | static void usage(void) 62 | { 63 | printf("Usage: mergelogs -p penlog [-j jitter] \\\n" 64 | " server1:logfile1 [server2:logfile2 ...]\n\n" 65 | " -c Do not use penlog cache\n" 66 | " -d Debugging (repeat for more)\n" 67 | " -p penlog Log file from pen\n" 68 | " -j jitter Jitter in seconds [2]\n" 69 | " -r filename Where to put rejects\n" 70 | " -t seconds Timezone\n" 71 | " server:logfile Web server address and name of logfile\n"); 72 | exit(0); 73 | } 74 | 75 | static int options(int argc, char **argv) 76 | { 77 | int c; 78 | 79 | while ((c = getopt(argc, argv, "p:a:j:t:cd")) != -1) { 80 | switch (c) { 81 | case 'p': 82 | pfile = optarg; 83 | break; 84 | case 'j': 85 | jitter = atoi(optarg); 86 | break; 87 | case 't': 88 | tz = atoi(optarg); 89 | break; 90 | case 'c': 91 | cache_penlog = 0; 92 | break; 93 | case 'd': 94 | debuglevel++; 95 | break; 96 | default: 97 | usage(); 98 | } 99 | } 100 | return optind; 101 | } 102 | 103 | static int mon2num(char *p) 104 | { 105 | int i; 106 | 107 | for (i = 0; i < 12; i++) 108 | if (!strcmp(p, months[i])) return i; 109 | return -1; 110 | } 111 | 112 | static char *num2mon(int m) 113 | { 114 | if (m < 0 || m > 11) return "no such month"; 115 | return months[m]; 116 | } 117 | 118 | static void thenp(char *b, time_t t) 119 | { 120 | struct tm *tms = localtime(&t); 121 | snprintf(b, 1024, "%02d/%s/%04d:%02d:%02d:%02d +0000", 122 | tms->tm_mday, num2mon(tms->tm_mon), tms->tm_year+1900, 123 | tms->tm_hour, tms->tm_min, tms->tm_sec); 124 | } 125 | 126 | /* 127 | Time format: 09/Jan/2002:00:27:15 +0100 128 | */ 129 | static time_t whenp(char *p, struct tm *tms) 130 | { 131 | char dd[100], mm[100], yy[100]; 132 | char hh[100], mi[100], ss[100], tz[100]; 133 | time_t t; 134 | 135 | tz[0] = '\0'; 136 | sscanf(p, "%[^/]/%[^/]/%[^:]:%[^:]:%[^:]:%s %s", 137 | dd, mm, yy, hh, mi, ss, tz); 138 | tms->tm_sec = atoi(ss); 139 | tms->tm_min = atoi(mi); 140 | tms->tm_hour = atoi(hh); 141 | tms->tm_mday = atoi(dd); 142 | tms->tm_mon = mon2num(mm); 143 | tms->tm_year = atoi(yy)-1900; 144 | t = mktime(tms); 145 | if (t != -1 && strlen(tz) == 5) { 146 | int d = 60*atoi(tz+3); 147 | tz[3] = '\0'; 148 | d += 3600*atoi(tz+1); 149 | if (tz[0] == '+') t -= d; 150 | else t += d; 151 | } 152 | return t; 153 | } 154 | 155 | static void read_server_line(int s) 156 | { 157 | char b[1024]; 158 | struct tm tms; 159 | int n; 160 | 161 | do { 162 | if (fgets(b, sizeof b, servers[s].fp) == NULL) { 163 | fclose(servers[s].fp); 164 | servers[s].fp = NULL; 165 | return; 166 | } 167 | 168 | n = sscanf(b, "%[^ ] %[^[][%[^]]]%[^\"]\"%[^\"]\"%[^\n]", 169 | servers[s].cli, servers[s].b1, servers[s].tim, 170 | servers[s].b2, servers[s].uri, servers[s].b3); 171 | if (n == 6) { 172 | servers[s].t = whenp(servers[s].tim, &tms); 173 | } else if (debuglevel) { 174 | debug("Read %d fields", n); 175 | } 176 | } while (n != 6); 177 | } 178 | 179 | static int oldest_server(void) 180 | { 181 | int i, n = -1; 182 | time_t t = -1; 183 | for (i = 0; i < nservers; i++) { 184 | if (servers[i].fp) { 185 | if (t == -1 || servers[i].t < t) { 186 | n = i; 187 | t = servers[i].t; 188 | } 189 | } 190 | } 191 | return n; 192 | } 193 | 194 | typedef struct { 195 | char *cli; /* client address */ 196 | int ser; /* server index (in servers array) */ 197 | time_t t; /* time stamp */ 198 | char *uri; /* uri */ 199 | } pencache; 200 | 201 | static pencache *pc; 202 | static int npc; 203 | 204 | static int server2num(char *s) 205 | { 206 | int i; 207 | for (i = 0; i < nservers; i++) { 208 | if (!strcmp(servers[i].a, s)) return i; 209 | } 210 | return -1; 211 | } 212 | 213 | /* cache relevant penlog lines to speed up the search */ 214 | static void best_client1(char *p, char *s, long t, char *u) 215 | { 216 | char b[1024], from[1024], to[1024], uri[1024]; 217 | long when, td, ntd; 218 | int i, j; 219 | int ser = server2num(s); 220 | 221 | /* first remove all entries that are older than (t-jitter) */ 222 | for (i = 0; i < npc; i++) { 223 | if (pc[i].t >= (t-jitter)) break; 224 | } 225 | if (i) { 226 | if (debuglevel) debug("uncache %d lines\n", i); 227 | for (j = 0; j < i; j++) { 228 | if (debuglevel >= 2) { 229 | debug("uncache '%s %ld %d %s'", 230 | pc[j].cli, pc[j].t, 231 | pc[j].ser, pc[j].uri); 232 | } 233 | free(pc[j].cli); 234 | free(pc[j].uri); 235 | } 236 | while (j < npc) { 237 | pc[j-i] = pc[j]; 238 | j++; 239 | } 240 | npc -= i; 241 | } 242 | 243 | /* then add entries until eof or newer than (t+jitter) */ 244 | for (;;) { 245 | if (npc > 0 && pc[npc-1].t > (t+jitter)) break; 246 | if (feof(pfp)) break; 247 | if (fgets(b, sizeof b, pfp) == NULL) break; 248 | pc = pen_realloc(pc, (npc+1)*sizeof *pc); 249 | if (debuglevel) debug("pc = %p, b = '%s'", pc, b); 250 | if (sscanf(b, "%s %ld %s %[^\n]", from, &when, to, uri) != 4) { 251 | continue; 252 | } 253 | pc[npc].cli = pen_strdup(from); 254 | pc[npc].ser = server2num(to); 255 | pc[npc].t = when-tz; 256 | pc[npc].uri = pen_strdup(uri); 257 | if (debuglevel >= 2) { 258 | debug("cache '%s %ld %d %s'", 259 | pc[npc].cli, pc[npc].t, 260 | pc[npc].ser, pc[npc].uri); 261 | } 262 | npc++; 263 | } 264 | 265 | /* now search the cache for a best match */ 266 | snprintf(p, 1024, "%s", s); 267 | td = LONG_MAX; 268 | 269 | for (i = 0; i < npc; i++) { 270 | if (ser != pc[i].ser) continue; 271 | if (strcmp(u, pc[i].uri)) continue; 272 | ntd = labs(t-pc[i].t); 273 | if (ntd < td) { 274 | td = ntd; 275 | snprintf(p, 1024, "%s", pc[i].cli); 276 | } 277 | } 278 | } 279 | 280 | /* same again, this time without the cache */ 281 | static void best_client0(char *p, char *s, long t, char *u) 282 | { 283 | char b[1024], from[1024], to[1024], uri[1024]; 284 | long when, td, ntd; 285 | rewind(pfp); 286 | snprintf(p, 1024, "%s", s); /* default is client = server */ 287 | td = LONG_MAX; 288 | while (fgets(b, sizeof b, pfp)) { 289 | if (sscanf(b, "%s %ld %s %[^\n]", from, &when, to, uri) != 4) { 290 | continue; 291 | } 292 | if (strcmp(s, to)) continue; 293 | if (strcmp(u, uri)) continue; 294 | when -= tz; 295 | ntd = labs(t-when); 296 | if (ntd < td) { 297 | td = ntd; 298 | snprintf(p, 1024, "%s", from); 299 | } 300 | } 301 | if (debuglevel && td > 600) { 302 | debug("Warning: time difference %ld", td); 303 | } 304 | } 305 | 306 | static void best_client(char *p, char *s, long t, char *u) 307 | { 308 | if (cache_penlog) best_client1(p, s, t, u); 309 | else best_client0(p, s, t, u); 310 | } 311 | 312 | int main(int argc, char **argv) 313 | { 314 | int i, n, s; 315 | 316 | n = options(argc, argv); 317 | argc -= n; 318 | argv += n; 319 | if (argc < 1) { 320 | usage(); 321 | } 322 | if (pfile == NULL) error("pfile null"); 323 | pfp = fopen(pfile, "r"); 324 | if (pfp == NULL) error("pfp null"); 325 | 326 | s = 0; 327 | servers = pen_calloc(argc, sizeof *servers); 328 | for (i = 0; i < argc; i++) { 329 | servers[s].a = pen_strdup(argv[i]); 330 | servers[s].fn = strchr(servers[s].a, ':'); 331 | if (servers[s].fn == NULL) 332 | error("Bogus server '%s'\n", argv[i]); 333 | *servers[s].fn++ = '\0'; 334 | servers[s].fp = fopen(servers[s].fn, "r"); 335 | if (servers[s].fp == NULL) 336 | error("Can't open logfile '%s'\n", servers[s].fn); 337 | read_server_line(s); 338 | nservers++; 339 | s++; 340 | } 341 | 342 | /* 343 | Example log lines: 344 | 10.0.18.6 - - [09/Jan/2002:00:28:50 +0100] "GET /robots.txt HTTP/1.0" 404 268 345 | 10.0.18.6 - - [09/Jan/2002:00:28:50 +0100] "GET /news.html HTTP/1.0" 200 4017 346 | 347 | That is: client, whatever, whatever, [timestamp], "URI", code, size. 348 | */ 349 | 350 | while ((s = oldest_server()) != -1) { 351 | char cli[1024], tim[1024]; 352 | 353 | thenp(tim, servers[s].t); 354 | best_client(cli, servers[s].a, servers[s].t, servers[s].uri); 355 | if (debuglevel >= 2) { 356 | debug("\tclient = '%s' => '%s'", 357 | servers[s].cli, cli); 358 | debug("\ttime = '%s' => %d => '%s'", 359 | servers[s].cli, cli, servers[s].tim, 360 | (int)servers[s].t, tim); 361 | debug("\turi = '%s'", 362 | servers[s].uri); 363 | } 364 | printf("%s %s[%s]%s\"%s\"%s\n", 365 | cli, servers[s].b1, tim, 366 | servers[s].b2, servers[s].uri, servers[s].b3); 367 | read_server_line(s); 368 | } 369 | fclose(pfp); 370 | return 0; 371 | } 372 | -------------------------------------------------------------------------------- /netconv.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifdef WINDOWS 8 | #include 9 | #include 10 | #include 11 | #else 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #endif 18 | #include "diag.h" 19 | #include "windows.h" 20 | 21 | /* return port number in host byte order */ 22 | int getport(char *p, int proto) 23 | { 24 | struct servent *s = getservbyname(p, proto == SOCK_STREAM ? "tcp" : "udp"); 25 | if (s == NULL) { 26 | return atoi(p); 27 | } else { 28 | return ntohs(s->s_port); 29 | } 30 | } 31 | 32 | /* Takes a struct sockaddr_storage and returns the port number in host order. 33 | For a Unix socket, the port number is 1. 34 | */ 35 | int pen_getport(struct sockaddr_storage *a) 36 | { 37 | struct sockaddr_in *si; 38 | struct sockaddr_in6 *si6; 39 | 40 | switch (a->ss_family) { 41 | case AF_UNIX: 42 | return 1; 43 | case AF_INET: 44 | si = (struct sockaddr_in *)a; 45 | return ntohs(si->sin_port); 46 | case AF_INET6: 47 | si6 = (struct sockaddr_in6 *)a; 48 | return ntohs(si6->sin6_port); 49 | default: 50 | debug("pen_getport: Unknown address family %d", a->ss_family); 51 | } 52 | return 0; 53 | } 54 | 55 | int pen_setport(struct sockaddr_storage *a, int port) 56 | { 57 | struct sockaddr_in *si; 58 | struct sockaddr_in6 *si6; 59 | 60 | switch (a->ss_family) { 61 | case AF_UNIX: 62 | /* No port for Unix domain sockets */ 63 | return 1; 64 | case AF_INET: 65 | si = (struct sockaddr_in *)a; 66 | si->sin_port = htons(port); 67 | return 1; 68 | case AF_INET6: 69 | si6 = (struct sockaddr_in6 *)a; 70 | si6->sin6_port = htons(port); 71 | return 1; 72 | default: 73 | debug("pen_setport: Unknown address family %d", a->ss_family); 74 | } 75 | return 0; 76 | } 77 | 78 | /* Takes a struct sockaddr_storage and returns the name in a static buffer. 79 | The address can be a unix socket path or an ipv4 or ipv6 address. 80 | */ 81 | char *pen_ntoa(struct sockaddr_storage *a) 82 | { 83 | static char b[1024]; 84 | struct sockaddr_in *si; 85 | struct sockaddr_in6 *si6; 86 | #ifndef WINDOWS 87 | struct sockaddr_un *su; 88 | #endif 89 | 90 | switch (a->ss_family) { 91 | case AF_INET: 92 | si = (struct sockaddr_in *)a; 93 | snprintf(b, sizeof b, "%s", inet_ntoa(si->sin_addr)); 94 | break; 95 | case AF_INET6: 96 | si6 = (struct sockaddr_in6 *)a; 97 | if (inet_ntop(AF_INET6, &si6->sin6_addr, b, sizeof b) == NULL) { 98 | debug("pen_ntoa: can't convert address"); 99 | strncpy(b, "(cannot convert address)", sizeof b); 100 | } 101 | break; 102 | #ifndef WINDOWS 103 | case AF_UNIX: 104 | su = (struct sockaddr_un *)a; 105 | snprintf(b, sizeof b, "%s", su->sun_path); 106 | break; 107 | #endif 108 | default: 109 | debug("pen_ntoa: unknown address family %d", a->ss_family); 110 | snprintf(b, sizeof b, "(unknown address family %d", a->ss_family); 111 | } 112 | return b; 113 | } 114 | 115 | void pen_dumpaddr(struct sockaddr_storage *a) 116 | { 117 | switch (a->ss_family) { 118 | case AF_INET: 119 | debug("Family: AF_INET"); 120 | debug("Port: %d", pen_getport(a)); 121 | debug("Address: %s", pen_ntoa(a)); 122 | break; 123 | case AF_INET6: 124 | debug("Family: AF_INET6"); 125 | debug("Port: %d", pen_getport(a)); 126 | debug("Address: %s", pen_ntoa(a)); 127 | break; 128 | #ifndef WINDOWS 129 | case AF_UNIX: 130 | debug("Family: AF_UNIX"); 131 | debug("Path: %s", pen_ntoa(a)); 132 | break; 133 | #endif 134 | default: 135 | debug("pen_dumpaddr: Unknown address family %d", a->ss_family); 136 | } 137 | } 138 | 139 | int pen_ss_size(struct sockaddr_storage *ss) 140 | { 141 | switch (ss->ss_family) { 142 | #ifndef WINDOWS 143 | case AF_UNIX: 144 | return sizeof(struct sockaddr_un); 145 | #endif 146 | case AF_INET: 147 | return sizeof(struct sockaddr_in); 148 | case AF_INET6: 149 | return sizeof(struct sockaddr_in6); 150 | default: 151 | debug("pen_ss_size: unknown address family %d", ss->ss_family); 152 | return sizeof(struct sockaddr_storage); 153 | } 154 | } 155 | 156 | /* Takes a name and fills in a struct sockaddr_storage. The port is left alone. 157 | The address can be a unix socket path, a host name, an ipv4 address or an ipv6 address. 158 | Returns 0 for failure and 1 for success. 159 | */ 160 | int pen_aton(char *name, struct sockaddr_storage *addr) 161 | { 162 | struct sockaddr_in *si; 163 | struct sockaddr_in6 *si6; 164 | #ifndef WINDOWS 165 | struct sockaddr_un *su; 166 | #endif 167 | struct addrinfo *ai; 168 | struct addrinfo hints; 169 | int n, result; 170 | 171 | DEBUG(2, "pen_aton(%s, %p)", name, addr); 172 | #ifndef WINDOWS 173 | /* Deal with Unix domain sockets first */ 174 | if (strchr(name, '/')) { 175 | addr->ss_family = AF_UNIX; 176 | su = (struct sockaddr_un *)addr; 177 | snprintf(su->sun_path, sizeof su->sun_path, "%s", name); 178 | return 1; 179 | } 180 | #endif 181 | memset(&hints, 0, sizeof(hints)); 182 | hints.ai_flags = AI_ADDRCONFIG; 183 | hints.ai_socktype = SOCK_STREAM; 184 | n = getaddrinfo(name, NULL, &hints, &ai); 185 | if (n != 0) { 186 | debug("getaddrinfo: %s", gai_strerror(n)); 187 | return 0; 188 | } 189 | DEBUG(2, "family = %d\nsocktype = %d\nprotocol = %d\n" \ 190 | "addrlen = %d\nsockaddr = %d\ncanonname = %s", \ 191 | ai->ai_family, ai->ai_socktype, ai->ai_protocol, \ 192 | (int)ai->ai_addrlen, ai->ai_addr, ai->ai_canonname); 193 | addr->ss_family = ai->ai_family; 194 | switch (ai->ai_family) { 195 | case AF_INET: 196 | si = (struct sockaddr_in *)addr; 197 | /* ai->ai_addr is a struct sockaddr * */ 198 | /* (struct sockaddr_in *)ai->ai_addr is a struct sockaddr_in * */ 199 | /* ((struct sockaddr_in *)ai->ai_addr)->sin_addr is a struct in_addr */ 200 | si->sin_addr = ((struct sockaddr_in *)ai->ai_addr)->sin_addr; 201 | result = 1; 202 | break; 203 | case AF_INET6: 204 | si6 = (struct sockaddr_in6 *)addr; 205 | /* ai->ai_addr is a struct sockaddr * */ 206 | /* (struct sockaddr_in6 *)ai->ai_addr is a struct sockaddr_in6 * */ 207 | /* ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr is a struct in6_addr */ 208 | si6->sin6_addr = ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr; 209 | result = 1; 210 | break; 211 | default: 212 | debug("Unknown family %d", ai->ai_family); 213 | result = 0; 214 | break; 215 | } 216 | freeaddrinfo(ai); 217 | return result; 218 | } 219 | -------------------------------------------------------------------------------- /netconv.h: -------------------------------------------------------------------------------- 1 | extern int getport(char *, int); 2 | extern int pen_setport(struct sockaddr_storage *, int); 3 | extern int pen_getport(struct sockaddr_storage *); 4 | extern char *pen_ntoa(struct sockaddr_storage *); 5 | extern void pen_dumpaddr(struct sockaddr_storage *); 6 | extern int pen_ss_size(struct sockaddr_storage *); 7 | extern int pen_aton(char *, struct sockaddr_storage *); 8 | -------------------------------------------------------------------------------- /pen-ocsp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Run from cron like so: 4 | # 0 * * * * /usr/local/bin/pen-ocsp.sh >> /var/log/pen-ocsp.log 2>&1 5 | 6 | # Suggested file hierarchy: 7 | # /etc/pen # General configuration files 8 | # /etc/pen/sni # Private keys, certificates et al, including default certificate 9 | # /etc/pen/sni/example.com.key # Private key for example.com 10 | # /etc/pen/sni/example.com.crt # Certificate for example.com 11 | # /etc/pen/sni/example.com.ca # CA's certificate for example.com 12 | # /etc/pen/sni/example.com.ocsp # OCSP response file, auto-loaded by this script 13 | # /etc/pen/sni/example.net.key # Private key for example.net 14 | # /etc/pen/sni/example.net.crt # Certificate for example.net 15 | # /etc/pen/sni/example.net.ca # CA's certificate for example.net 16 | # /etc/pen/sni/example.net.ocsp # OCSP response file, auto-loaded by this script 17 | 18 | # Pen *requires* a default certificate in order to enable SSL. It is suggested that 19 | # this certificate is placed in /etc/pen/sni with the other certificates. 20 | 21 | CFG=/etc/pen 22 | SNI=$CFG/sni 23 | CTL=/var/run/pen/https.ctl 24 | 25 | # Sample domains, default domain first: 26 | DOMAINS="example.com example.net" 27 | 28 | # No changes should be necessary below this line. 29 | 30 | 31 | # get_ocsp cert cacert outfile 32 | get_ocsp() 33 | { 34 | uri=`openssl x509 -noout -ocsp_uri -in $1` 35 | if test -z "$uri"; then 36 | echo "No OCSP URI found in cert" 37 | else 38 | host=`echo "$uri"|cut -f 3 -d /` 39 | openssl ocsp -noverify -issuer "$2" -cert "$1" -url "$uri" -header Host "$host" -respout "$3.tmp" 40 | if test -s "$3.tmp"; then 41 | mv "$3.tmp" "$3" 42 | else 43 | echo "No response for $1" 44 | fi 45 | fi 46 | } 47 | 48 | for d in $DOMAINS; do 49 | get_ocsp $SNI/$d.crt $SNI/$d.ca $SNI/$d.ocsp 50 | done 51 | 52 | # Tell Pen to reload the ocsp response for the default ssl context. 53 | set $DOMAINS 54 | if test -s "$SNI/$1.ocsp"; then 55 | penctl "$CTL" "ssl_ocsp_response" "$SNI/$1.ocsp" 56 | else 57 | echo "No response for default domain $1" 58 | fi 59 | 60 | -------------------------------------------------------------------------------- /pen.1: -------------------------------------------------------------------------------- 1 | .TH PEN 1 LOCAL 2 | 3 | .SH NAME 4 | pen - Load balancer for udp and tcp based protocols 5 | 6 | .SH SYNOPSIS 7 | .B pen 8 | .na 9 | [-b sec] [-c N] [-e host:port] [-t sec] [-x N] [-j dir] [-u user] [-F cfgfile] [-l logfile] [-p file ] [-w file] [-C port|/path/to/socket] [-T sec] [-UHWXadfhrs] [-o option] [-E certfile] [-K keyfile] [-G cacertfile] [-A cacertdir] [-Z] [-R] [-L protocol] [host:]port|/path/to/socket h1[:p1[:maxc1[:hard1[:weight1[:prio1]]]]] [h2[:p2[:maxc2[:hard2[:weight2[:prio2]]]]]] ... 10 | .ad 11 | 12 | Windows only: 13 | 14 | .B pen 15 | -i service_name 16 | 17 | .B pen 18 | -u service_name 19 | 20 | .SH EXAMPLE 21 | pen 80 www1:8000:10 www2:80:10 www3 22 | 23 | Here three servers cooperate in a web server farm. Host www1 runs its 24 | web server on port 8000 and accepts a maximum of 10 simultaneous connections. 25 | Host www2 runs on port 80 and accepts 10 connections. Finally, www3 runs 26 | its web server on port 80 and allows an unlimited number of simultaneous 27 | connections. 28 | 29 | .SH DESCRIPTION 30 | .I Pen 31 | is a load balancer for udp and tcp based protocols such as 32 | dns, http or smtp. It allows several servers to appear as one to the 33 | outside and automatically detects servers that are down and distributes 34 | clients among the available servers. This gives high availability and 35 | scalable performance. 36 | 37 | The load balancing algorithm keeps track of clients and will try to 38 | send them back to the server they visited the last time. The client 39 | table has a number of slots (default 2048, settable through command-line 40 | arguments). When the table is full, the least recently used one will 41 | be thrown out to make room for the new one. 42 | 43 | This is superior to a simple round-robin algorithm, which sends a client 44 | that connects repeatedly to different servers. Doing so breaks 45 | applications that maintain state between connections in the server, 46 | including most modern web applications. 47 | 48 | When pen detects that a server is unavailable, it scans for another 49 | starting with the server after the most recently used one. That way 50 | we get load balancing and "fair" failover for free. 51 | 52 | Correctly configured, pen can ensure that a server farm is always 53 | available, even when individual servers are brought down for maintenance 54 | or reconfiguration. The final single point of failure, pen itself, 55 | can be eliminated by running pen on several servers, using vrrp to 56 | decide which is active. 57 | 58 | Sending pen a USR1 signal will make it print some useful statistics on stderr, 59 | even if debugging is disabled. If pen is running in the background (i.e. 60 | without the -f option), syslog is used rather than stderr. If the 61 | -w option is used, the statistics is saved in HTML format in the 62 | given file. 63 | 64 | Sending pen a HUP signal will make it close and reopen the logfile, 65 | if logging is enabled, and reload the configuration file. 66 | 67 | Rotate the log like this (assuming pen.log 68 | is the name of the logfile): 69 | 70 | mv pen.log pen.log.1 71 | kill -HUP `cat ` 72 | 73 | where is the file containing pen's process id, as written by the -p option. 74 | 75 | Sending pen a TERM signal will make it exit cleanly, closing the 76 | log file and all open sockets. 77 | 78 | .SH OPTIONS 79 | .TP 80 | -C \fIport|/path/to/socket\fR 81 | Specifies a control port where the load balancer listens for commands. See penctl.1 for a list of the commands available. The protocol is unauthenticated and the administrator is expected to restrict access using an access control list (for connections over a network) or Unix file permissions (for a Unix domain socket). Pen will normally refuse to open the control port if running as root; see -u option. If you still insist that you want to run pen as root with a control port, use "-u root". 82 | .TP 83 | -F \fIcfgfile\fR 84 | Names a configuration file with commands in penctl format (see penctl.1). The file is read after processing all command line arguments, and also after receiving a HUP signal. 85 | .TP 86 | -H 87 | Adds X-Forwarded-For header to http requests. 88 | .TP 89 | -U 90 | Use udp protocol support 91 | .TP 92 | -O command 93 | Allows most penctl commands to be used on the Pen command line. 94 | .TP 95 | -P 96 | Use poll() for event notification. 97 | .TP 98 | -W 99 | Use weight for server selection. 100 | .TP 101 | -X 102 | Adds an exit command to the control interface. 103 | .TP 104 | -a 105 | Used in conjunction with -dd to get communication dumps in ascii 106 | rather than hexadecimal format. 107 | .TP 108 | -b \fIsec\fR 109 | Servers that do not respond are blacklisted, i.e. excluded from the 110 | server selection algorithm, for the specified number of seconds (default 30). 111 | .TP 112 | -T \fIsec\fR 113 | Clients are tracked for the specified number of seconds so they can be sent to the same server as the last time (default 0 = never expire clients). 114 | .TP 115 | -c \fIN\fR 116 | Max number of clients (default 2048). 117 | .TP 118 | -d 119 | Debugging (repeat -d for more). The output goes to stderr if we are running 120 | in the foreground (see -f) and to syslog (facility user, priority 121 | debug) otherwise. 122 | .TP 123 | -e \fIhost:port\fR 124 | host:port specifies the emergency server to contact if all regular 125 | servers become unavailable. 126 | .TP 127 | -f 128 | Stay in foreground. 129 | .TP 130 | -h 131 | Use a hash on the client IP address for the initial server selection. 132 | This makes it more predictable where clients will be connected. 133 | .TP 134 | -i \fIservice_name\fR 135 | Windows only. Install pen as a service. 136 | .TP 137 | -j \fIdir\fR 138 | Run in a chroot environment. 139 | .TP 140 | -l \fIfile\fR 141 | Turn on logging. 142 | .TP 143 | -m \fImulti_accept\fR 144 | Accept up to \fImulti_accept\fR incoming connections at a time. 145 | .TP 146 | -p \fIfile\fR 147 | Write the pid of the running daemon to \fIfile\fR. 148 | .TP 149 | -q \fIbacklog\fR 150 | Allow the queue of pending incoming connections to grow up to a maximum of \fIbacklog\fR entries. 151 | .TP 152 | -r 153 | Go straight into round-robin server selection without looking up 154 | which server a client used the last time. 155 | .TP 156 | -s 157 | Stubborn server selection: if the initial choice is unavailable, the 158 | client connection is closed without trying another server. 159 | .TP 160 | -t \fIsec\fR 161 | Connect timeout in seconds (default 5). 162 | .TP 163 | -u \fIuser\fR 164 | Posix only. Run as a different user. 165 | .TP 166 | -u \fIservice_name\fR 167 | Windows only. Uninstall the service. 168 | .TP 169 | -x \fIN\fR 170 | Max number of simultaneous connections (default 500). 171 | .TP 172 | -w \fIfile\fR 173 | File for status reports in HTML format. 174 | .TP 175 | -o \fIoption\fR 176 | Use option in penctl format. 177 | .TP 178 | -E \fIcertfile\fR 179 | Use the given certificate in PEM format. 180 | .TP 181 | -K \fIkeyfile\fR 182 | Use the given key in PEM format (may be contained in cert). 183 | .TP 184 | -G \fIcacertfile\fR 185 | File containing the CA's certificate. 186 | .TP 187 | -A \fIcacertdir\fR 188 | Directory containing CA certificates in hashed format. 189 | .TP 190 | -Z 191 | Use SSL compatibility mode. 192 | .TP 193 | -R 194 | Require valid peer certificate. 195 | .TP 196 | -L \fIprotocol\fR 197 | ssl23 (default), ssl3 or tls1. 198 | .TP 199 | [host:]port OR /path/to/socket 200 | The local address and port pen listens to. By default pen listens to 201 | all local addresses. Pen can also use a Unix domain socket as the local 202 | listening address. 203 | .TP 204 | h1:p1:soft:hard:weight:prio 205 | The address, port and maximum number of simultaneous connections for 206 | a remote server. By default, the port is the same as the local port, 207 | and the soft limit on the number of connections is unlimited. The hard 208 | limit is used for clients which have accessed the server before. 209 | The weight and prio are used for the weight- and priority-based 210 | server selection algorithms. 211 | 212 | .SH LIMITATIONS 213 | Pen runs in a single process, and opens two sockets for each connection. 214 | Depending on kernel configuration, pen can run out of file descriptors. 215 | 216 | SSL support is available if pen was built with the --with-ssl option. 217 | 218 | GeoIP support is available if pen was built with the --with-geoip option. 219 | 220 | .SH SEE ALSO 221 | penctl(1), dwatch(1), mergelogs(1), webresolve(1) 222 | 223 | .SH AUTHOR 224 | Copyright (C) 2001-2016 Ulric Eriksson, . 225 | 226 | .SH ACKNOWLEDGEMENTS 227 | In part inspired by balance by Thomas Obermair. 228 | -------------------------------------------------------------------------------- /pen.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifdef WINDOWS 4 | #include 5 | #include 6 | #include 7 | extern void stop_winsock(); 8 | #else 9 | #include 10 | #endif 11 | 12 | //#ifdef HAVE_LIBSSL 13 | //#include 14 | //#include 15 | //#endif /* HAVE_LIBSSL */ 16 | 17 | extern int socket_nb(int, int, int); 18 | 19 | extern int listenfd; 20 | extern time_t now; 21 | extern struct sockaddr_storage *source; 22 | 23 | extern void mainloop(void); 24 | 25 | -------------------------------------------------------------------------------- /pen.spec: -------------------------------------------------------------------------------- 1 | # DarContact: Ulric Eriksson 2 | 3 | Summary: A load balancer for "simple" tcp based protocols. 4 | Name: pen 5 | Version: 0.11.0 6 | Release: 0 7 | License: GPL 8 | Group: Applications/Internet 9 | URL: http://siag.nu/pen/ 10 | 11 | Packager: Dag Wieers 12 | Vendor: Dag Apt Repository, http://dag.wieers.com/apt/ 13 | 14 | Source: ftp://siag.nu/pub/pen/%{name}-%{version}.tar.gz 15 | BuildRoot: %{_tmppath}/root-%{name}-%{version} 16 | Prefix: %{_prefix} 17 | 18 | %description 19 | Pen is a load balancer for "simple" tcp based protocols such as http or smtp. 20 | It allows several servers to appear as one to the outside and automatically 21 | detects servers that are down and distributes clients among the available 22 | servers. This gives high availability and scalable performance. 23 | 24 | %prep 25 | %setup 26 | 27 | ### FIXME: Add a default pen.conf for Apache. (Please fix upstream) 28 | %{__cat} <%{name}.conf 29 | ScriptAlias /pen/ %{_localstatedir}/www/pen/ 30 | 31 | DirectoryIndex penctl.cgi 32 | Options ExecCGI 33 | order deny,allow 34 | deny from all 35 | allow from 127.0.0.1 36 | 37 | EOF 38 | 39 | %build 40 | %configure 41 | %{__make} %{?_smp_mflags} 42 | 43 | %install 44 | %{__rm} -rf %{buildroot} 45 | %makeinstall 46 | 47 | %{__install} -d -m0755 %{buildroot}%{_localstatedir}/www/pen/ \ 48 | %{buildroot}%{_sysconfdir}/httpd/conf.d/ 49 | %{__install} -m0755 penctl.cgi %{buildroot}%{_localstatedir}/www/pen/ 50 | %{__install} -m0644 pen.conf %{buildroot}%{_sysconfdir}/httpd/conf.d/ 51 | 52 | 53 | %post 54 | if [ -f %{_sysconfdir}/httpd/conf/httpd.conf ]; then 55 | if ! grep -q "Include .*/pen.conf" %{_sysconfdir}/httpd/conf/httpd.conf; then 56 | echo -e "\n# Include %{_sysconfdir}/httpd/conf.d/pen.conf" >> %{_sysconfdir}/httpd/conf/httpd.conf 57 | # /sbin/service httpd restart 58 | fi 59 | fi 60 | 61 | %clean 62 | %{__rm} -rf %{buildroot} 63 | 64 | %files 65 | %defattr(-, root, root, 0755) 66 | %doc AUTHORS ChangeLog COPYING HOWTO NEWS README TODO 67 | %doc %{_mandir}/man?/* 68 | %config(noreplace) %{_sysconfdir}/httpd/conf.d/*.conf 69 | %{_bindir}/* 70 | %{_localstatedir}/www/pen/ 71 | 72 | %changelog 73 | * Tue Sep 23 2003 Dag Wieers - 0.11.0-0 74 | - Initial package. (using DAR) 75 | -------------------------------------------------------------------------------- /pen_epoll.h: -------------------------------------------------------------------------------- 1 | extern void epoll_init(void); 2 | -------------------------------------------------------------------------------- /pen_kqueue.h: -------------------------------------------------------------------------------- 1 | extern void kqueue_init(void); 2 | -------------------------------------------------------------------------------- /pen_poll.h: -------------------------------------------------------------------------------- 1 | extern void poll_init(void); 2 | -------------------------------------------------------------------------------- /pen_select.h: -------------------------------------------------------------------------------- 1 | extern void select_init(void); 2 | -------------------------------------------------------------------------------- /penctl.1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UlricE/pen/2c09b7a82122836ee26e6c7e6a141d0b44b724a6/penctl.1 -------------------------------------------------------------------------------- /penctl.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2000-2015 Ulric Eriksson 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2, or (at your option) 7 | any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, 17 | MA 02111-1307, USA. 18 | */ 19 | 20 | #include "config.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | //#include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #ifdef TIME_WITH_SYS_TIME 35 | #include 36 | #endif 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include "config.h" 48 | 49 | static void error(char *fmt, ...) 50 | { 51 | char b[4096]; 52 | va_list ap; 53 | va_start(ap, fmt); 54 | vsnprintf(b, sizeof b, fmt, ap); 55 | fprintf(stderr, "%s\n", b); 56 | va_end(ap); 57 | exit(EXIT_FAILURE); 58 | } 59 | 60 | static void alarm_handler(int dummy) 61 | { 62 | ; 63 | } 64 | 65 | static void usage(void) 66 | { 67 | printf("usage: penctl host:port command\n"); 68 | exit(0); 69 | } 70 | 71 | static int open_unix_socket(char *path) 72 | { 73 | int n, fd; 74 | struct sockaddr_un serv_addr; 75 | 76 | fd = socket(PF_UNIX, SOCK_STREAM, 0); 77 | if (fd < 0) error("error opening socket"); 78 | memset(&serv_addr, 0, sizeof serv_addr); 79 | serv_addr.sun_family = AF_UNIX; 80 | snprintf(serv_addr.sun_path, sizeof serv_addr.sun_path, "%s", path); 81 | n = connect(fd, (struct sockaddr *)&serv_addr, sizeof serv_addr); 82 | if (n == -1) { 83 | error("error connecting to server"); 84 | } 85 | return fd; 86 | } 87 | 88 | static int open_socket(char *addr, char *port) 89 | { 90 | int fd = -1; 91 | struct addrinfo *ai; 92 | struct addrinfo hints; 93 | struct addrinfo *runp; 94 | int n; 95 | memset(&hints, 0, sizeof(hints)); 96 | hints.ai_flags = AI_ADDRCONFIG; 97 | hints.ai_socktype = SOCK_STREAM; 98 | n = getaddrinfo(addr, port, &hints, &ai); 99 | if (n != 0) { 100 | error("getaddrinfo: %s", gai_strerror(n)); 101 | } 102 | runp = ai; 103 | /* only using first result; should try all */ 104 | fd = socket(runp->ai_family, runp->ai_socktype, runp->ai_protocol); 105 | 106 | if (fd < 0) error("error opening socket"); 107 | signal(SIGALRM, alarm_handler); 108 | n = connect(fd, runp->ai_addr, runp->ai_addrlen); 109 | alarm(0); 110 | if (n == -1) { 111 | error("error connecting to server"); 112 | } 113 | return fd; 114 | } 115 | 116 | int main(int argc, char **argv) 117 | { 118 | int i, fd, n; 119 | char b[1024], *p; 120 | 121 | if (argc < 3) { 122 | usage(); 123 | } 124 | 125 | if (strchr(argv[1], '/')) { 126 | fd = open_unix_socket(argv[1]); 127 | } else { 128 | n = 1+strlen(argv[1]); /* one for \0 */ 129 | if (n > sizeof b) error("Overlong arg '%s'", argv[1]); 130 | snprintf(b, sizeof b, "%s", argv[1]); 131 | /* We need the *last* : to allow such arguments as ::1:10080 132 | if pen's control port is ipv6 localhost:10080 */ 133 | p = strrchr(b, ':'); 134 | if (p == NULL) error("no port given"); 135 | 136 | *p++ = '\0'; 137 | 138 | fd = open_socket(b, p); 139 | } 140 | 141 | n = 0; 142 | for (i = 2; argv[i]; i++) { 143 | for (p = argv[i]; *p; p++) { 144 | if (n >= (sizeof b)-1) error("Overlong argument list"); 145 | b[n++] = *p; 146 | } 147 | b[n++] = ' '; 148 | } 149 | if (n >= (sizeof b)-1) error("Overlong argument list"); 150 | b[--n] = '\n'; /* replace last ' ' */ 151 | b[++n] = '\0'; /* terminate string */ 152 | 153 | n = write(fd, b, strlen(b)); 154 | if (n == -1) error("error writing to socket"); 155 | for (;;) { 156 | n = read(fd, b, sizeof b); 157 | if (n == 0) break; 158 | if (n == -1) error("error reading from socket"); 159 | n = write(1, b, n); 160 | if (n == -1) error("error writing to stdout"); 161 | } 162 | close(fd); 163 | 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /penctl.cgi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright (C) 2002-2015 Ulric Eriksson 4 | 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2, or (at your option) 8 | # any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software 17 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, 18 | # MA 02111-1307, USA. 19 | 20 | PENCTL=penctl 21 | 22 | #set -x 23 | 24 | header() 25 | { 26 | if test -z "$1"; then 27 | TITLE=Penctl 28 | else 29 | TITLE="$1" 30 | fi 31 | cat << EOF 32 | 33 | 34 | $TITLE 35 | 36 | 37 |

$TITLE

38 | EOF 39 | } 40 | 41 | footer() 42 | { 43 | cat << EOF 44 | 45 | 46 | EOF 47 | } 48 | 49 | errorpage() 50 | { 51 | header "Error" 52 | echo "$1" 53 | footer 54 | exit 0 55 | } 56 | 57 | get_query() 58 | { 59 | echo "$QUERY_STRING"|tr '&' '\n'|grep "^$1="|cut -f 2 -d =|sed -e 's,%2F,/,g' -e 's,%..,,g' 60 | } 61 | 62 | statuspage() 63 | { 64 | test -z "$SERVER" && errorpage "No server" 65 | test -z "$PORT" && errorpage "No port" 66 | 67 | $PENCTL $SERVER:$PORT status 2> /path/to/tmp/penctl.cgi 68 | if test "$?" != "0"; then 69 | errorpage "`cat /path/to/tmp/penctl.cgi`" 70 | fi 71 | echo "
" 72 | echo '' 73 | echo '' 74 | echo '' 75 | echo "
" 76 | } 77 | 78 | # testmode modelist setting 79 | testmode() 80 | { 81 | echo "$1"|egrep "no(\+| )$2" > /dev/null 2>&1 82 | if test "$?" = "0"; then 83 | echo "no" 84 | else 85 | echo "yes" 86 | fi 87 | } 88 | 89 | # setmode setting old new 90 | setmode() 91 | { 92 | if test "$2" != "$3"; then 93 | if test "$3" = "yes"; then 94 | $PENCTL $SERVER:$PORT $1 95 | else 96 | $PENCTL $SERVER:$PORT no $1 97 | fi 98 | fi 99 | } 100 | 101 | settingspage() 102 | { 103 | test -z "$SERVER" && errorpage "No server" 104 | test -z "$PORT" && errorpage "No port" 105 | 106 | OLDMODE=`get_query oldmode` 107 | if test ! -z "$OLDMODE"; then 108 | BLACKLIST=`get_query blacklist` 109 | test -z "$BLACKLIST" || $PENCTL $SERVER:$PORT blacklist $BLACKLIST 110 | DEBUG=`get_query debug` 111 | test -z "$DEBUG" || $PENCTL $SERVER:$PORT debug $DEBUG 112 | LOG=`get_query log` 113 | test -z "$LOG" || $PENCTL $SERVER:$PORT log $LOG 114 | TIMEOUT=`get_query timeout` 115 | test -z "$TIMEOUT" || $PENCTL $SERVER:$PORT timeout $TIMEOUT 116 | TRACKING=`get_query tracking` 117 | test -z "$TRACKING" || $PENCTL $SERVER:$PORT tracking $TRACKING 118 | WEB_STATS=`get_query web_stats` 119 | test -z "$WEB_STATS" || $PENCTL $SERVER:$PORT web_stats $WEB_STATS 120 | x=`testmode "$OLDMODE" block` 121 | BLOCK=`get_query block` 122 | setmode "block" "$x" "$BLOCK" 123 | x=`testmode "$OLDMODE" delayed_forward` 124 | DFORWARD=`get_query dforward` 125 | setmode "dforward" "$x" "$DFORWARD" 126 | x=`testmode "$OLDMODE" hash` 127 | HASH=`get_query hash` 128 | setmode "hash" "$x" "$HASH" 129 | # HTTP=`get_query http` 130 | # setmode "$OLDMODE" "$x" "$HTTP" 131 | x=`testmode "$OLDMODE" roundrobin` 132 | ROUNDROBIN=`get_query roundrobin` 133 | setmode "roundrobin" "$x" "$ROUNDROBIN" 134 | x=`testmode "$OLDMODE" stubborn` 135 | STUBBORN=`get_query stubborn` 136 | setmode "stubborn" "$x" "$STUBBORN" 137 | fi 138 | 139 | cat << EOF 1>&2 140 | BLOCK="$BLOCK" 141 | DFORWARD="$DFORWARD" 142 | HASH="$HASH" 143 | ROUNDROBIN="$ROUNDROBIN" 144 | STUBBORN="$STUBBORN" 145 | EOF 146 | 147 | header "Global Settings" 148 | BLACKLIST=`$PENCTL $SERVER:$PORT blacklist` 149 | CLIENTS_MAX=`$PENCTL $SERVER:$PORT clients_max` 150 | CONN_MAX=`$PENCTL $SERVER:$PORT conn_max` 151 | DEBUG=`$PENCTL $SERVER:$PORT debug` 152 | LISTEN=`$PENCTL $SERVER:$PORT listen` 153 | LOG=`$PENCTL $SERVER:$PORT log` 154 | MODE=`$PENCTL $SERVER:$PORT mode` 155 | echo "

MODE=$MODE

" 156 | BLOCK=`testmode "$MODE" block` 157 | DFORWARD=`testmode "$MODE" delayed_forward` 158 | HASH=`testmode "$MODE" hash` 159 | ROUNDROBIN=`testmode "$MODE" roundrobin` 160 | STUBBORN=`testmode "$MODE" stubborn` 161 | PID=`$PENCTL $SERVER:$PORT pid` 162 | TIMEOUT=`$PENCTL $SERVER:$PORT timeout` 163 | TRACKING=`$PENCTL $SERVER:$PORT tracking` 164 | WEB_STATS=`$PENCTL $SERVER:$PORT web_stats` 165 | 166 | BLOCKCHECKED="" 167 | test x$BLOCK = xyes && BLOCKCHECKED=checked 168 | DFORWARDCHECKED="" 169 | test x$DFORWARD = xyes && DFORWARDCHECKED=checked 170 | HASHCHECKED="" 171 | test x$HASH = xyes && HASHCHECKED=checked 172 | ROUNDROBINCHECKED="" 173 | test x$ROUNDROBIN = xyes && ROUNDROBINCHECKED=checked 174 | STUBBORNCHECKED="" 175 | test x$STUBBORN = xyes && STUBBORNCHECKED=checked 176 | 177 | cat << EOF 178 |
179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
Blacklist time
Max # of clients$CLIENTS_MAX
Max # of connections$CONN_MAX
Debug level
Listening port$LISTEN
Logging destination
Blocking
Delayed forward
Hash
Roundrobin
Stubborn
Process ID$PID
Connect timeout
Client tracking
Web status report
197 | 198 | 199 | 200 | 201 | 202 |
203 | EOF 204 | 205 | footer 206 | } 207 | 208 | managepage() 209 | { 210 | test -z "$SERVER" && errorpage "No server" 211 | test -z "$PORT" && errorpage "No port" 212 | 213 | N=0 214 | A=`get_query "A.$N"` 215 | while test ! -z "$A"; do 216 | P=`get_query "P.$N"` 217 | M=`get_query "M.$N"` 218 | H=`get_query "H.$N"` 219 | T=`get_query "T.$N"` 220 | x="" 221 | test -z "$A" || x="$x address $A" 222 | test -z "$P" || x="$x port $P" 223 | test -z "$M" || x="$x max $M" 224 | test -z "$H" || x="$x hard $H" 225 | test -z "$T" || x="$x blacklist $T" 226 | test -z "$x" || $PENCTL $SERVER:$PORT server $N $x 227 | # N=`echo $N+1|bc` 228 | N=$((N+1)) 229 | A=`get_query "A.$N"` 230 | done 231 | 232 | header "Manage Servers" 233 | cat << EOF 234 |
235 | 236 | 249 |
Server 237 | Address 238 | Port 239 | Conn 240 | Max 241 | Hard 242 | Sx 243 | Rx 244 | Blacklist 245 | EOF 246 | $PENCTL $SERVER:$PORT servers | while read N a A p P c C m M h H s S r R; do 247 | cat << EOF 248 |
$N 250 | 251 | 252 | $C 253 | 254 | 255 | $S 256 | $R 257 | 258 | EOF 259 | done 260 | cat << EOF 261 |
262 | 263 | 264 | 265 | 266 |
267 | EOF 268 | } 269 | 270 | mainpage() 271 | { 272 | header 273 | cat << EOF 274 |
275 |
Server 276 |
Port 277 |
278 | 279 | 280 | 281 |
282 | EOF 283 | footer 284 | } 285 | 286 | 287 | echo Content-type: text/html 288 | echo 289 | 290 | echo "This script has not been updated in many years and is unfit for public consumption" 291 | 292 | exit 293 | 294 | SERVER=`get_query server` 295 | PORT=`get_query port` 296 | MODE=`get_query mode` 297 | 298 | case "$MODE" in 299 | Status ) 300 | statuspage 301 | ;; 302 | Manage ) 303 | managepage 304 | ;; 305 | Settings ) 306 | settingspage 307 | ;; 308 | * ) 309 | mainpage 310 | ;; 311 | esac 312 | 313 | -------------------------------------------------------------------------------- /penlog.1: -------------------------------------------------------------------------------- 1 | .TH PENLOG 1 LOCAL 2 | 3 | .SH NAME 4 | penlog - pipe Apache logs to penlogd 5 | 6 | .SH SYNOPSIS 7 | .B penlog 8 | server_ip server_port [my_ip] 9 | 10 | .SH EXAMPLE 11 | penlog lbhost:10000 12 | 13 | .SH DESCRIPTION 14 | .I Penlog 15 | reads webserver log entries from stdin and sends them using UDP to 16 | penlogd. It is intended for Apache's "reliable piped logs". 17 | To use penlog from Apache, add a command 18 | like this to the web server's httpd.conf: 19 | 20 | CustomLog "|/usr/local/bin/penlog somehost 10000" common 21 | 22 | The optional third argument is used if the server has several addresses. 23 | Penlogd uses the source address to identify the server, and it must be 24 | identical to the address configured in the command line to Pen. 25 | 26 | .SH SEE ALSO 27 | pen(1), penlogd(1) 28 | 29 | .SH AUTHOR 30 | Copyright (C) 2002-2015 Ulric Eriksson, . 31 | -------------------------------------------------------------------------------- /penlog.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2000-2015 Ulric Eriksson 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2, or (at your option) 7 | any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, 17 | MA 02111-1307, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "diag.h" 30 | 31 | #define MAXBUF 1024 32 | 33 | int main(int argc, char **argv) 34 | { 35 | int sk; 36 | struct addrinfo *client, *server; 37 | struct addrinfo hints; 38 | char buf[MAXBUF]; 39 | int n; 40 | 41 | if (argc < 3) { 42 | error("Usage: %s server_ip server_port [my_ip]", argv[0]); 43 | } 44 | 45 | memset(&hints, 0, sizeof hints); 46 | hints.ai_socktype = SOCK_DGRAM; 47 | hints.ai_flags = AI_ADDRCONFIG; 48 | n = getaddrinfo(argv[1], argv[2], &hints, &server); 49 | if (n != 0) { 50 | error("getaddrinfo: %s", gai_strerror(n)); 51 | } 52 | 53 | sk = socket(server->ai_family, server->ai_socktype, server->ai_protocol); 54 | if (sk == -1) { 55 | error("Problem creating socket"); 56 | } 57 | 58 | if (argc > 3) { /* set local address */ 59 | memset(&hints, 0, sizeof hints); 60 | hints.ai_socktype = SOCK_DGRAM; 61 | hints.ai_flags = AI_ADDRCONFIG; 62 | n = getaddrinfo(argv[3], NULL, &hints, &client); 63 | if (n != 0) { 64 | error("getaddrinfo: %s", gai_strerror(n)); 65 | } 66 | if (bind(sk, client->ai_addr, client->ai_addrlen) != 0) { 67 | error("Problem creating socket"); 68 | } 69 | } 70 | 71 | while (fgets(buf, sizeof buf, stdin)) { 72 | n = sendto(sk, buf, strlen(buf), 0, 73 | server->ai_addr, server->ai_addrlen); 74 | 75 | if (n < 0) { 76 | debug("Problem sending data: %s", strerror(errno)); 77 | } 78 | } 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /penlogd.1: -------------------------------------------------------------------------------- 1 | .TH PENLOGD 1 LOCAL 2 | 3 | .SH NAME 4 | penlogd - consolidate web server logs 5 | 6 | .SH SYNOPSIS 7 | .B penlogd 8 | [-fd] [-j dir] [-l logfile] [-n N] [-p pidfile] [-u user] port 9 | 10 | .SH EXAMPLE 11 | penlogd -l /var/log/access_log -p /var/run/penlogd.pid 10000 12 | 13 | .SH DESCRIPTION 14 | .I Penlogd 15 | receives log entries from Pen and from each of the web servers. It 16 | consolidates the entries by replacing the source addresses in each entry 17 | with the "real" client address and writes the result to stdout or to 18 | the file given on the command line. 19 | This completely removes the need for postprocessing with mergelogs, 20 | since the logs are already merged. 21 | 22 | Pen must be instructed to send its log to penlogd. See HOWTO and pen man page 23 | for details. 24 | 25 | Sending penlogd a HUP signal will make it close and reopen the logfile, unless 26 | it is logging to stdout. Rotate the log like this: 27 | 28 | mv access_log access_log.1 29 | kill -HUP `cat ` 30 | 31 | where is the file containing pen's process id. 32 | 33 | Sending penlogd a TERM signal will make it close the log file and exit cleanly. 34 | 35 | .SH OPTIONS 36 | .TP 37 | -d 38 | Turn on debugging. The output goes to stderr if we are running in the 39 | foreground (see -f) and to syslog (facility user, priority debug) 40 | otherwise. 41 | .TP 42 | -f 43 | Stay in foreground. 44 | .TP 45 | -j \fIdir\fR 46 | Run in a chroot environment. 47 | .TP 48 | -l \fIlogfile\fR 49 | Write output into logfile. 50 | .TP 51 | -n \fIN\fR 52 | Number of pen log entries to cache (default 1000). 53 | .TP 54 | -p \fIpidfile\fR 55 | Write process id into pidfile. 56 | .TP 57 | -u \fIuser\fR 58 | Run as a different user. 59 | .TP 60 | port 61 | The UDP port where penlogd receives log entries. 62 | 63 | .SH SEE ALSO 64 | pen(1), penlog(1), webresolve(1) 65 | 66 | .SH AUTHOR 67 | Copyright (C) 2002-2015 Ulric Eriksson, . 68 | -------------------------------------------------------------------------------- /penlogd.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (C) 2002-2015 Ulric Eriksson 3 | 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 2, or (at your option) 7 | any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program; if not, write to the Free Software 16 | Foundation, Inc., 59 Temple Place - Suite 330, Boston, 17 | MA 02111-1307, USA. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "diag.h" 36 | #include "settings.h" 37 | 38 | #define PEN_MAX 1000 /* make that at least 1000 for prod */ 39 | 40 | /* This structure is statically allocated, which is wasteful but saves us 41 | from frequest memory allocation. 42 | */ 43 | static struct penlog { 44 | struct in_addr addr; 45 | char client[100]; 46 | char request[100]; 47 | } *penlog; 48 | 49 | static int pen_n = 0; /* next slot in buffer */ 50 | 51 | static int unbuffer = 0; 52 | static char *logfile = NULL; 53 | static FILE *logfp; 54 | static char *pidfile = NULL; 55 | static int loopflag = 1; 56 | static int do_restart_log = 0; 57 | static int pen_max = PEN_MAX; 58 | static char *user = NULL, *jail = NULL; 59 | 60 | static struct sigaction hupaction, termaction; 61 | 62 | static void restart_log(int dummy) 63 | { 64 | do_restart_log = 1; 65 | sigaction(SIGHUP, &hupaction, NULL); 66 | } 67 | 68 | static void quit(int dummy) 69 | { 70 | loopflag = 0; 71 | } 72 | 73 | static void store_web(char *b, int n, struct in_addr addr) 74 | { 75 | char request[1024]; 76 | char *p, *q; 77 | int i, m; 78 | 79 | b[n] = '\0'; 80 | if (debuglevel > 1) debug("store_web(%s, %d)", b, n); 81 | p = strchr(b, '"'); 82 | if (p == NULL) { 83 | debug("bogus web line %s", b); 84 | return; 85 | } 86 | p++; 87 | q = strchr(p, '"'); 88 | if (q == NULL) { 89 | debug("bogus line %s", b); 90 | return; 91 | } 92 | memcpy(request, p, q-p); 93 | request[q-p] = '\0'; 94 | if (q-p < 100) m = q-p; 95 | else m = 100; 96 | i = pen_n-1; 97 | if (i < 0) i = pen_max-1; 98 | while (i != pen_n) { 99 | if (penlog[i].request[0] && 100 | addr.s_addr == penlog[i].addr.s_addr && 101 | !strncmp(request, penlog[i].request, m)) { 102 | break; 103 | } 104 | i--; 105 | if (i < 0) i = pen_max-1; 106 | } 107 | 108 | if (i == pen_n) { /* no match */ 109 | fwrite(b, 1, n, logfp); 110 | } else { 111 | fputs(penlog[i].client, logfp); 112 | p = strchr(b, ' '); 113 | if (p == NULL) { 114 | if (debuglevel) debug("Ugly"); 115 | return; 116 | } 117 | fwrite(p, 1, n-(p-b), logfp); 118 | } 119 | } 120 | 121 | static void store_pen(char *b, int n) 122 | { 123 | char client[100], server[100], request[100]; 124 | #ifdef HAVE_INET_ATON 125 | struct in_addr addr; 126 | #else 127 | struct hostent *hp; 128 | #endif 129 | 130 | b[n] = '\0'; 131 | if (sscanf(b, "+ %99[^ ] %99[^ ] %99[^\n]", 132 | client, server, request) != 3) { 133 | debug("discarding bogus pen line %s", b); 134 | return; 135 | } 136 | if (debuglevel > 1) 137 | debug("store_pen(%i: %s, %s, %s)",pen_n, client, server, request); 138 | 139 | #ifdef HAVE_INET_ATON 140 | if (inet_aton(server, &addr) == 0) { 141 | debug("bogus address %s", server); 142 | return; 143 | } 144 | penlog[pen_n].addr = addr; 145 | #else 146 | hp = gethostbyname(server); 147 | memcpy(&penlog[pen_n].addr, hp->h_addr, hp->h_length); 148 | #endif 149 | 150 | 151 | strncpy(penlog[pen_n].client, client, 100); 152 | strncpy(penlog[pen_n].request, request, 100); 153 | pen_n++; 154 | if (pen_n >= pen_max) pen_n = 0; 155 | } 156 | 157 | static void usage(void) 158 | { 159 | printf("Usage:\n" 160 | " penlogd [options] port\n" 161 | "\n" 162 | " -d debugging on\n" 163 | " -f stay in foreground\n" 164 | " -j dir run in chroot\n" 165 | " -l file write log to file\n" 166 | " -b unbuffer output (Testing Only!)\n" 167 | " -n N number of pen log entries to cache [1000]\n" 168 | " -p file write pid to file\n" 169 | " -u user run as alternative user\n"); 170 | exit(0); 171 | } 172 | 173 | static void background(void) 174 | { 175 | #ifdef HAVE_DAEMON 176 | daemon(0, 0); 177 | #else 178 | int childpid; 179 | if ((childpid = fork()) < 0) { 180 | error("Can't fork"); 181 | } else { 182 | if (childpid > 0) exit(0); /* parent */ 183 | } 184 | setsid(); 185 | signal(SIGCHLD, SIG_IGN); 186 | #endif 187 | } 188 | 189 | static int options(int argc, char **argv) 190 | { 191 | int c; 192 | 193 | while ((c = getopt(argc, argv, "j:l:n:p:u:dfb")) != -1) { 194 | switch (c) { 195 | case 'd': 196 | debuglevel++; 197 | break; 198 | case 'b': 199 | unbuffer = 1; 200 | break; 201 | case 'f': 202 | foreground = 1; 203 | break; 204 | case 'j': 205 | jail = optarg; 206 | break; 207 | case 'l': 208 | logfile = optarg; 209 | break; 210 | case 'n': 211 | pen_max = atoi(optarg); 212 | break; 213 | case 'p': 214 | pidfile = optarg; 215 | break; 216 | case 'u': 217 | user = optarg; 218 | break; 219 | default: 220 | usage(); 221 | } 222 | } 223 | 224 | return optind; 225 | } 226 | 227 | int main(int argc, char **argv) 228 | { 229 | struct passwd *pwd = NULL; 230 | struct sockaddr_in a; 231 | socklen_t len; 232 | int ld, p; 233 | char b[1024]; 234 | 235 | int n = options(argc, argv); 236 | argc -= n; 237 | argv += n; 238 | 239 | if (argc < 1 || (p = atoi(argv[0])) == 0) { 240 | usage(); 241 | exit(0); 242 | } 243 | 244 | penlog = malloc(pen_max * sizeof *penlog); 245 | if (!penlog) error("Can't allocate penlog"); 246 | 247 | if (!foreground) background(); 248 | 249 | if (user) { 250 | if (debuglevel) debug("Run as user %s", user); 251 | pwd = getpwnam(user); 252 | if (pwd == NULL) error("Can't getpwnam(%s)", user); 253 | } 254 | if (jail) { 255 | if (debuglevel) debug("Run in %s", jail); 256 | if (chroot(jail) == -1) error("Can't chroot(%s)", jail); 257 | } 258 | if (pwd) { 259 | if (setuid(pwd->pw_uid) == -1) 260 | error("Can't setuid(%d)", (int)pwd->pw_uid); 261 | } 262 | if (logfile) { 263 | if (debuglevel) debug("Logging to %s", logfile); 264 | logfp = fopen(logfile, "a"); 265 | if (!logfp) error("Can't open logfile %s", logfile); 266 | if (unbuffer) setvbuf(logfp, (char *)NULL, _IOLBF, 0); 267 | } else { 268 | if (debuglevel) debug("Logging to stdout"); 269 | logfp = stdout; 270 | } 271 | if (pidfile) { 272 | FILE *pidfp = fopen(pidfile, "w"); 273 | if (debuglevel) { 274 | debug("Writing pid %d to %s", 275 | (int)getpid(), pidfile); 276 | } 277 | if (!pidfp) error("Can't create pidfile %s", pidfile); 278 | fprintf(pidfp, "%d", (int)getpid()); 279 | fclose(pidfp); 280 | } 281 | 282 | if ((ld = socket(PF_INET, SOCK_DGRAM, 0)) < 0) { 283 | error("Problem creating socket"); 284 | } 285 | 286 | a.sin_family = AF_INET; 287 | a.sin_addr.s_addr = htonl(INADDR_ANY); 288 | a.sin_port = htons(p); 289 | 290 | if (bind(ld, (struct sockaddr *) &a, sizeof(a)) < 0) { 291 | error("Problem binding"); 292 | } 293 | 294 | len = sizeof a; 295 | if (getsockname(ld, (struct sockaddr *) &a, &len) < 0) { 296 | error("Error getsockname"); 297 | } 298 | 299 | hupaction.sa_handler = restart_log; 300 | sigemptyset(&hupaction.sa_mask); 301 | hupaction.sa_flags = 0; 302 | sigaction(SIGHUP, &hupaction, NULL); 303 | termaction.sa_handler = quit; 304 | sigemptyset(&termaction.sa_mask); 305 | termaction.sa_flags = 0; 306 | sigaction(SIGTERM, &termaction, NULL); 307 | 308 | loopflag = 1; 309 | 310 | if (debuglevel) debug("Enter main loop\n"); 311 | 312 | while (loopflag) { 313 | if (do_restart_log) { 314 | if (debuglevel) debug("Reopen log file %s", logfile); 315 | if (logfp != stdout) { 316 | fclose(logfp); 317 | logfp = fopen(logfile, "a"); 318 | if (!logfp) error("Can't open %s", logfile); 319 | if (unbuffer) setvbuf(logfp, (char *)NULL, _IOLBF, 0); 320 | } 321 | do_restart_log = 0; 322 | } 323 | n = recvfrom(ld, b, sizeof b, 0, (struct sockaddr *) &a, &len); 324 | 325 | if (n < 0) { 326 | if (errno != EINTR) 327 | debug("Error receiving data: %s", strerror(errno)); 328 | continue; 329 | } 330 | b[n] = 0; 331 | if (b[0] == '+') { 332 | store_pen(b, n); 333 | } else { 334 | store_web(b, n, a.sin_addr); 335 | } 336 | } 337 | 338 | if (debuglevel) debug("Exit main loop"); 339 | 340 | if (logfp) fclose(logfp); 341 | if (pidfile) unlink(pidfile); 342 | return 0; 343 | } 344 | -------------------------------------------------------------------------------- /penstats: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PENHOME=/home/ulric/Projekt/pen 4 | PIDFILE=$PENHOME/pid 5 | WEBFILE=$PENHOME/webstats.html 6 | 7 | # This will make pen save its stats 8 | kill -USR1 `cat $PIDFILE` 9 | 10 | # We don't know how long it will take; wait a few seconds 11 | sleep 2 12 | 13 | # And display the results 14 | echo "Content-type: text/html" 15 | echo 16 | cat $WEBFILE 17 | 18 | -------------------------------------------------------------------------------- /poll.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | //#include "pen.h" 7 | #include "diag.h" 8 | #include "event.h" 9 | #include "memory.h" 10 | #ifdef HAVE_POLL 11 | #include 12 | 13 | static struct pollfd *poll_ufds; 14 | static int poll_nfds, poll_count, poll_nfds_max; 15 | static int pindex; 16 | 17 | /* Making a sparse pollfd table, using fd as the index seems most efficient. */ 18 | /* Need to make sure to grow the table as necessary. */ 19 | 20 | static void poll_event_ctl(int fd, int events) 21 | { 22 | int pollevents = 0; 23 | DEBUG(2, "poll_event_ctl(fd=%d, events=%d)", fd, events); 24 | if (fd >= poll_nfds_max) { 25 | int i, new_max = fd+10000; 26 | DEBUG(2, "expanding poll_ufds to %d entries", new_max); 27 | poll_ufds = pen_realloc(poll_ufds, new_max * sizeof *poll_ufds); 28 | for (i = poll_nfds_max; i < new_max; i++) { 29 | poll_ufds[i].fd = -1; 30 | poll_ufds[i].events = 0; 31 | } 32 | poll_nfds_max = new_max; 33 | } 34 | if (events & EVENT_READ) pollevents |= POLLIN; 35 | if (events & EVENT_WRITE) pollevents |= POLLOUT; 36 | poll_ufds[fd].fd = fd; 37 | poll_ufds[fd].events = pollevents; 38 | if (fd >= poll_nfds) poll_nfds = fd+1; 39 | } 40 | 41 | static void poll_event_add(int fd, int events) 42 | { 43 | DEBUG(2, "poll_event_add(fd=%d, events=%d)", fd, events); 44 | poll_event_ctl(fd, events); 45 | } 46 | 47 | static void poll_event_arm(int fd, int events) 48 | { 49 | DEBUG(2, "poll_event_arm(fd=%d, events=%d)", fd, events); 50 | poll_event_ctl(fd, events); 51 | } 52 | 53 | static void poll_event_delete(int fd) 54 | { 55 | DEBUG(2, "poll_event_delete(fd=%d)", fd); 56 | poll_ufds[fd].fd = -1; /* ignore events */ 57 | poll_ufds[fd].events = 0; 58 | } 59 | 60 | static void poll_event_wait(void) 61 | { 62 | DEBUG(2, "poll_event_wait()"); 63 | pindex = -1; 64 | poll_count = poll(poll_ufds, poll_nfds, 1000*timeout); 65 | DEBUG(2, "poll returns %d", poll_count); 66 | if (poll_count < 0 && errno != EINTR) { 67 | error("Error on poll: %s", strerror(errno)); 68 | } 69 | } 70 | 71 | static int poll_event_fd(int *revents) 72 | { 73 | int events = 0; 74 | DEBUG(2, "poll_event_fd(revents=%p)", revents); 75 | for (pindex++; pindex < poll_nfds; pindex++) { 76 | DEBUG(3, "\tpoll_ufds[%d] = {fd=%d, revents=%d}", pindex, poll_ufds[pindex].fd, poll_ufds[pindex].revents); 77 | if (poll_ufds[pindex].revents & POLLIN) events |= EVENT_READ; 78 | if (poll_ufds[pindex].revents & POLLOUT) events |= EVENT_WRITE; 79 | if (events) { 80 | *revents = events; 81 | return poll_ufds[pindex].fd; 82 | } 83 | } 84 | return -1; 85 | } 86 | 87 | void poll_init(void) 88 | { 89 | DEBUG(2, "poll_init()"); 90 | poll_nfds = poll_nfds_max = 0; 91 | poll_ufds = NULL; 92 | event_add = poll_event_add; 93 | event_arm = poll_event_arm; 94 | event_delete = poll_event_delete; 95 | event_wait = poll_event_wait; 96 | event_fd = poll_event_fd; 97 | } 98 | #else 99 | void poll_init(void) 100 | { 101 | debug("You don't have poll"); 102 | exit(EXIT_FAILURE); 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /select.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef WINDOWS 7 | #include 8 | #endif 9 | #if defined HAVE_SYS_SELECT_H && !defined WINDOWS 10 | /* because windows doesn't have it */ 11 | #include 12 | #endif 13 | //#include "pen.h" 14 | #include "conn.h" 15 | #include "diag.h" 16 | #include "event.h" 17 | 18 | static fd_set w_read, w_write; 19 | static fd_set w_r_copy, w_w_copy; 20 | static int w_max; 21 | 22 | static void select_event_ctl(int fd, int events) 23 | { 24 | DEBUG(2, "select_event_ctl(fd=%d, events=%d)", fd, events); 25 | if (events & EVENT_READ) FD_SET(fd, &w_read); 26 | else FD_CLR(fd, &w_read); 27 | if (events & EVENT_WRITE) FD_SET(fd, &w_write); 28 | else FD_CLR(fd, &w_write); 29 | if (events) { 30 | if (fd >= w_max) w_max = fd+1; 31 | } 32 | } 33 | 34 | static void select_event_add(int fd, int events) 35 | { 36 | DEBUG(2, "select_event_add(fd=%d, events=%d)", fd, events); 37 | select_event_ctl(fd, events); 38 | } 39 | 40 | static void select_event_arm(int fd, int events) 41 | { 42 | DEBUG(2, "select_event_arm(fd=%d, events=%d)", fd, events); 43 | select_event_ctl(fd, events); 44 | } 45 | 46 | static void select_event_delete(int fd) 47 | { 48 | DEBUG(2, "select_event_delete(fd=%d)", fd); 49 | FD_CLR(fd, &w_read); 50 | FD_CLR(fd, &w_write); 51 | } 52 | 53 | static int fd; 54 | 55 | static void select_event_wait(void) 56 | { 57 | int n, err; 58 | struct timeval tv; 59 | DEBUG(2, "select_event_wait()"); 60 | tv.tv_sec = timeout; 61 | tv.tv_usec = 0; 62 | memcpy(&w_r_copy, &w_read, sizeof w_read); 63 | memcpy(&w_w_copy, &w_write, sizeof w_write); 64 | fd = -1; 65 | n = select(w_max, &w_r_copy, &w_w_copy, 0, &tv); 66 | err = socket_errno; 67 | DEBUG(2, "select returns %d, socket_errno=%d", n, err); 68 | if (n < 0 && err != EINTR) { 69 | error("Error on select: %s", strerror(errno)); 70 | } 71 | } 72 | 73 | static int select_event_fd(int *revents) 74 | { 75 | int events = 0; 76 | DEBUG(2, "select_event_fd(revents=%p)", revents); 77 | for (fd++; fd < w_max; fd++) { 78 | if (FD_ISSET(fd, &w_r_copy)) events |= EVENT_READ; 79 | if (FD_ISSET(fd, &w_w_copy)) events |= EVENT_WRITE; 80 | if (events) { 81 | *revents = events; 82 | return fd; 83 | } 84 | } 85 | return -1; 86 | } 87 | 88 | void select_init(void) 89 | { 90 | DEBUG(2, "select_init()"); 91 | if ((connections_max*2+10) > FD_SETSIZE) { 92 | error("Number of simultaneous connections too large.\n" 93 | "Maximum is %d, or re-build pen with larger FD_SETSIZE", 94 | (FD_SETSIZE-10)/2); 95 | } 96 | FD_ZERO(&w_read); 97 | FD_ZERO(&w_write); 98 | w_max = 0; 99 | event_add = select_event_add; 100 | event_arm = select_event_arm; 101 | event_delete = select_event_delete; 102 | event_wait = select_event_wait; 103 | event_fd = select_event_fd; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /server.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #ifndef WINDOWS 8 | #include 9 | #endif 10 | #ifdef HAVE_LIBSSL 11 | #include 12 | #include 13 | #endif 14 | 15 | #include "acl.h" 16 | #include "client.h" 17 | #include "conn.h" 18 | #include "diag.h" 19 | #include "dlist.h" 20 | #include "event.h" 21 | #include "memory.h" 22 | #include "netconv.h" 23 | #include "pen.h" 24 | #include "server.h" 25 | #include "settings.h" 26 | #include "windows.h" 27 | 28 | #ifndef WINDOWS 29 | #define CONNECT_IN_PROGRESS (EINPROGRESS) 30 | #endif 31 | 32 | int nservers = 0; 33 | server *servers = NULL; 34 | 35 | int current; /* current server */ 36 | int emerg_server = NO_SERVER; /* server of last resort */ 37 | static int emergency = 0; /* are we using the emergency server? */ 38 | int abuse_server = NO_SERVER; /* server for naughty clients */ 39 | int blacklist_time = BLACKLIST_TIME; 40 | int server_alg; 41 | char *e_server = NULL; 42 | char *a_server = NULL; 43 | 44 | static int pen_hash(struct sockaddr_storage *a) 45 | { 46 | struct sockaddr_in *si; 47 | struct sockaddr_in6 *si6; 48 | unsigned char *u; 49 | int hash; 50 | 51 | switch (a->ss_family) { 52 | case AF_INET: 53 | si = (struct sockaddr_in *)a; 54 | if (server_alg & ALG_ROUNDROBIN) { 55 | hash = (si->sin_addr.s_addr ^ si->sin_port) % (nservers?nservers:1); 56 | } else { 57 | hash = si->sin_addr.s_addr % (nservers?nservers:1); 58 | } 59 | 60 | DEBUG(2, "Hash: %d", hash); 61 | 62 | return hash; 63 | 64 | case AF_INET6: 65 | si6 = (struct sockaddr_in6 *)a; 66 | u = (unsigned char *)(&si6->sin6_addr); 67 | return u[15] % (nservers?nservers:1); 68 | default: 69 | return 0; 70 | } 71 | } 72 | 73 | /* Introduce the new format "[address]:port:maxc:hard:weight:prio" 74 | in addition to the old one. 75 | */ 76 | void setaddress(int server, char *s, int dp, int proto) 77 | { 78 | char address[1024], pno[100]; 79 | int n; 80 | char *format; 81 | int port; 82 | 83 | if (s[0] == '[') { 84 | format = "[%999[^]]]:%99[^:]:%d:%d:%d:%d"; 85 | } else { 86 | format = "%999[^:]:%99[^:]:%d:%d:%d:%d"; 87 | } 88 | n = sscanf(s, format, address, pno, 89 | &servers[server].maxc, &servers[server].hard, 90 | &servers[server].weight, &servers[server].prio); 91 | 92 | if (n > 1) port = getport(pno, proto); 93 | else port = dp; 94 | if (n < 3) servers[server].maxc = 0; 95 | if (n < 4) servers[server].hard = 0; 96 | if (n < 5) servers[server].weight = 0; 97 | if (n < 6) servers[server].prio = 0; 98 | 99 | DEBUG(2, "n = %d, address = %s, pno = %d, maxc1 = %d, hard = %d, weight = %d, prio = %d, proto = %d ", \ 100 | n, address, port, servers[server].maxc, \ 101 | servers[server].hard, servers[server].weight, \ 102 | servers[server].prio, proto); 103 | 104 | if (pen_aton(address, &servers[server].addr) == 0) { 105 | error("unknown or invalid address [%s]", address); 106 | } 107 | memset(servers[server].hwaddr, 0, 6); 108 | pen_setport(&servers[server].addr, port); 109 | } 110 | 111 | void blacklist_server(int server) 112 | { 113 | servers[server].status = now; 114 | } 115 | 116 | int unused_server_slot(int i) 117 | { 118 | struct sockaddr_storage *a = &servers[i].addr; 119 | if (a->ss_family == AF_INET) { 120 | struct sockaddr_in *si = (struct sockaddr_in *)a; 121 | if (si->sin_addr.s_addr == 0) return i; 122 | } 123 | return 0; 124 | } 125 | 126 | int server_is_blacklisted(int i) 127 | { 128 | return (now-servers[i].status < blacklist_time); 129 | } 130 | 131 | int server_is_unavailable(int i) 132 | { 133 | return unused_server_slot(i) || server_is_blacklisted(i); 134 | } 135 | 136 | static int server_by_weight(void) 137 | { 138 | int best_server = NO_SERVER; 139 | int best_load = -1; 140 | int i, load; 141 | 142 | DEBUG(2, "server_by_weight()"); 143 | for (i = 0; i < nservers; i++) { 144 | if (server_is_unavailable(i)) continue; 145 | if (servers[i].weight == 0) continue; 146 | load = (WEIGHT_FACTOR*servers[i].c)/servers[i].weight; 147 | if (best_server == NO_SERVER || load < best_load) { 148 | DEBUG(2, "Server %d has load %d", i, load); 149 | best_load = load; 150 | best_server = i; 151 | } 152 | } 153 | DEBUG(2, "Least loaded server = %d", best_server); 154 | return best_server; 155 | } 156 | 157 | static int server_by_prio(void) 158 | { 159 | int best_server = NO_SERVER; 160 | int best_prio = -1; 161 | int i, prio; 162 | 163 | DEBUG(2, "server_by_prio()"); 164 | for (i = 0; i < nservers; i++) { 165 | if (server_is_unavailable(i)) continue; 166 | prio = servers[i].prio; 167 | if (best_server == NO_SERVER || prio < best_prio) { 168 | DEBUG(2, "Server %d has prio %d", i, prio); 169 | best_prio = prio; 170 | best_server = i; 171 | } 172 | } 173 | DEBUG(2, "Best prio server = %d", best_server); 174 | return best_server; 175 | } 176 | 177 | int server_by_roundrobin(void) 178 | { 179 | static int last_server = 0; 180 | int i = last_server; 181 | 182 | if (nservers == 0) return NO_SERVER; 183 | do { 184 | i = (i+1) % (nservers?nservers:1); 185 | DEBUG(3, "server_by_roundrobin considering server %d", i); 186 | if (!server_is_unavailable(i)) return (last_server = i); 187 | DEBUG(3, "server %d is unavailable, try next one", i); 188 | } while (i != last_server); 189 | return NO_SERVER; 190 | } 191 | 192 | /* Suggest a server for the initial field of the connection. 193 | Return NO_SERVER if none available. 194 | */ 195 | int initial_server(int conn) 196 | { 197 | int pd = match_acl(client_acl, &clients[conns[conn].client].addr); 198 | if (!pd) { 199 | DEBUG(1, "initial_server: denied by acl"); 200 | return abuse_server; 201 | /* returning abuse_server is correct even if it is not set 202 | because it defaults to NO_SERVER */ 203 | } 204 | if (!(server_alg & ALG_ROUNDROBIN)) { 205 | // Load balancing with memory == No roundrobin 206 | int server = clients[conns[conn].client].server; 207 | /* server may be NO_SERVER if this is a new client */ 208 | if (server != NO_SERVER && server != emerg_server && server != abuse_server) { 209 | DEBUG(2, "Will try previous server %d for client %d", server, conns[conn].client); 210 | return server; 211 | } 212 | } 213 | if (server_alg & ALG_PRIO) return server_by_prio(); 214 | if (server_alg & ALG_WEIGHT) return server_by_weight(); 215 | if (server_alg & ALG_HASH) return pen_hash(&clients[conns[conn].client].addr); 216 | return server_by_roundrobin(); 217 | } 218 | 219 | /* Returns 1 if a failover server candidate is available. 220 | Close connection and return 0 if none was found. 221 | */ 222 | int failover_server(int conn) 223 | { 224 | int server = conns[conn].server; 225 | DEBUG(2, "failover_server(%d): server = %d", conn, server); 226 | if (server_alg & ALG_STUBBORN) { 227 | DEBUG(2, "Won't failover because we are stubborn"); 228 | close_conn(conn); 229 | return 0; 230 | } 231 | if (server == ABUSE_SERVER) { 232 | DEBUG(2, "Won't failover from abuse server (%d)", abuse_server); 233 | close_conn(conn); 234 | return 0; 235 | } 236 | if (server == EMERGENCY_SERVER) { 237 | DEBUG(2, "Already using emergency server (%d), won't fail over", emerg_server); 238 | close_conn(conn); 239 | return 0; 240 | } 241 | if (conns[conn].upfd != -1) { 242 | if (conns[conn].state & CS_IN_PROGRESS) { 243 | pending_list = dlist_remove(conns[conn].pend); 244 | pending_queue--; 245 | } 246 | 247 | close(conns[conn].upfd); 248 | conns[conn].upfd = -1; 249 | } 250 | /* there needs to be at least two regular servers in order to fail over to something else */ 251 | /* and if we couldn't find a candidate for initial_server, we're not going to find one now */ 252 | if (nservers > 1 && server != NO_SERVER) { 253 | DEBUG(2, "Trying to find failover server. server = %d, initial = %d, nservers = %d", server, conns[conn].initial, nservers); 254 | do { 255 | server = (server+1) % nservers; 256 | DEBUG(2, "Intend to try server %d", server); 257 | if (try_server(server, conn)) return 1; 258 | } while (server != conns[conn].initial); 259 | } 260 | DEBUG(1, "using emergency server, remember to reset flag"); 261 | emergency = 1; 262 | if (try_server(emerg_server, conn)) return 1; 263 | close_conn(conn); 264 | return 0; 265 | } 266 | 267 | /* Using os-specific, similar but incompatible techniques, attempt to be transparent by 268 | setting our local upstream address to the client's address */ 269 | static void spoof_bind(int server, int conn, int upfd) 270 | { 271 | #if defined(IP_TRANSPARENT) /* Linux */ 272 | #define SOL_TRANSPARENCY SOL_IP 273 | #define PEN_TRANSPARENCY IP_TRANSPARENT 274 | #elif defined(SO_BINDANY) /* OpenBSD */ 275 | #define SOL_TRANSPARENCY SOL_SOCKET 276 | #define PEN_TRANSPARENCY SO_BINDANY 277 | #elif defined(IP_BINDANY) /* FreeBSD */ 278 | #define SOL_TRANSPARENCY IPPROTO_IP 279 | #define PEN_TRANSPARENCY IP_BINDANY 280 | #else 281 | #undef PEN_TRANSPARENCY 282 | #endif 283 | 284 | #ifdef PEN_TRANSPARENCY 285 | int client = conns[conn].client; 286 | int n; 287 | int one = 1; 288 | struct sockaddr_storage *sss = &servers[server].addr; 289 | struct sockaddr_storage *css = &clients[client].addr; 290 | struct sockaddr_in *caddr, addr; 291 | DEBUG(1, "spoof_bind(server = %d, conn = %d, upfd = %d)", server, conn, upfd); 292 | DEBUG(1, "client = %d", client); 293 | if (sss->ss_family != AF_INET || css->ss_family != AF_INET) { 294 | DEBUG(1, "server family = %d", sss->ss_family); 295 | DEBUG(1, "client family = %d", css->ss_family); 296 | debug("No transparency for incompatible families"); 297 | return; 298 | } 299 | n = setsockopt(upfd, SOL_TRANSPARENCY, PEN_TRANSPARENCY, &one, sizeof one); 300 | if (n == -1) { 301 | DEBUG(1, "upfd = %d", upfd); 302 | debug("setsockopt: %s", strerror(errno)); 303 | return; 304 | } 305 | caddr = (struct sockaddr_in *)css; 306 | addr.sin_family = caddr->sin_family; 307 | addr.sin_port = 0; 308 | addr.sin_addr.s_addr = caddr->sin_addr.s_addr; 309 | n = bind(upfd, (struct sockaddr *)&addr, sizeof addr); 310 | if (n == -1) { 311 | DEBUG(1, "upfd = %d", upfd); 312 | debug("bind: %s", strerror(errno)); 313 | return; 314 | } 315 | #else 316 | debug("You are trying to be transparent, but it is not supported"); 317 | #endif 318 | } 319 | 320 | /* Initiate connection to server 'index' and populate upfd field in connection */ 321 | /* return 1 for (potential) success, 0 for failure */ 322 | int try_server(int index, int conn) 323 | { 324 | int upfd; 325 | int client = conns[conn].client; 326 | int n = 0, err; 327 | int optval = 1; 328 | struct sockaddr_storage *addr = &servers[index].addr; 329 | /* The idea is that a client should be able to connect again to the same server 330 | even if the server is close to its configured connection limit */ 331 | int sticky = ((client != -1) && (index == clients[client].server)); 332 | 333 | if (index == NO_SERVER) { 334 | DEBUG(2, "Won't try to connect to NO_SERVER"); 335 | return 0; /* out of bounds */ 336 | } 337 | DEBUG(2, "Trying server %d for connection %d at time %d", index, conn, now); 338 | if (pen_getport(addr) == 0) { 339 | DEBUG(1, "No port for you!"); 340 | return 0; 341 | } 342 | if (now-servers[index].status < blacklist_time) { 343 | DEBUG(1, "Server %d is blacklisted", index); 344 | return 0; 345 | } 346 | if (servers[index].maxc != 0 && 347 | (servers[index].c >= servers[index].maxc) && 348 | (sticky == 0 || servers[index].c >= servers[index].hard)) { 349 | DEBUG(1, "Server %d is overloaded: sticky=%d, maxc=%d, hard=%d", \ 350 | index, sticky, servers[index].maxc, servers[index].hard); 351 | return 0; 352 | } 353 | if ((client != -1) && !match_acl(servers[index].acl, &(clients[client].addr))) { 354 | DEBUG(1, "try_server: denied by acl"); 355 | return 0; 356 | } 357 | upfd = socket_nb(addr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0); 358 | 359 | if (keepalive) { 360 | setsockopt(upfd, SOL_SOCKET, SO_KEEPALIVE, (void *)&optval, sizeof optval); 361 | } 362 | 363 | if (debuglevel > 1) { 364 | debug("Connecting to %s", pen_ntoa(addr)); 365 | pen_dumpaddr(addr); 366 | } 367 | conns[conn].t = now; 368 | 369 | if (source) { 370 | /* specify local address for upstream connection */ 371 | int n = bind(upfd, (struct sockaddr *)source, pen_ss_size(source)); 372 | if (n == -1) { 373 | debug("bind: %s", strerror(errno)); 374 | } 375 | } else if (transparent) { 376 | /* use originating client's address for upstream connection */ 377 | spoof_bind(index, conn, upfd); 378 | } 379 | 380 | n = connect(upfd, (struct sockaddr *)addr, pen_ss_size(addr)); 381 | err = socket_errno; 382 | DEBUG(2, "connect (upfd = %d) returns %d, errno = %d, socket_errno = %d", 383 | upfd, n, errno, err); 384 | /* A new complication is that we don't know yet if the connect will succeed. */ 385 | if (n == 0) { /* connection completed */ 386 | conns[conn].state = CS_CONNECTED; 387 | if (conns[conn].downfd == -1) { 388 | /* idler */ 389 | conns[conn].state |= CS_CLOSED_DOWN; 390 | } 391 | event_add(upfd, EVENT_READ); 392 | event_add(conns[conn].downfd, EVENT_READ); 393 | servers[index].c++; 394 | if (servers[index].status) { 395 | servers[index].status = 0; 396 | DEBUG(1, "Server %d ok", index); 397 | } 398 | DEBUG(2, "Successful connect to server %d\n" \ 399 | "conns[%d].client = %d\n" \ 400 | "conns[%d].server = %d", \ 401 | index, conn, conns[conn].client, conn, conns[conn].server); 402 | } else if (err == CONNECT_IN_PROGRESS) { /* may potentially succeed */ 403 | conns[conn].state = CS_IN_PROGRESS; 404 | pending_list = dlist_insert(pending_list, conn); 405 | conns[conn].pend = pending_list; 406 | pending_queue++; 407 | event_add(upfd, EVENT_WRITE); 408 | DEBUG(2, "Pending connect to server %d\n" \ 409 | "conns[%d].client = %d\n" \ 410 | "conns[%d].server = %d", \ 411 | index, conn, conns[conn].client, conn, conns[conn].server); 412 | } else { /* failed definitely */ 413 | if (servers[index].status == 0) { 414 | debug("Server %d failed, retry in %d sec: %d", 415 | index, blacklist_time, socket_errno); 416 | } 417 | debug("blacklisting server %d because connect error %d", index, err); 418 | blacklist_server(index); 419 | close(upfd); 420 | return 0; 421 | } 422 | conns[conn].server = index; 423 | DEBUG(2, "Setting server %d for client %d", index, client); 424 | clients[client].server = index; 425 | current = index; 426 | conns[conn].upfd = upfd; 427 | fd2conn_set(upfd, conn); 428 | return 1; 429 | } 430 | 431 | /* we want size server slots plus two for abuse and emergency */ 432 | void expand_servertable(int size) 433 | { 434 | static server *server_storage = NULL; 435 | static int real_size = 0; 436 | int new_size = size+2; /* for emergency and abuse servers */ 437 | if (new_size <= real_size) return; /* nothing to expand here */ 438 | server_storage = pen_realloc(server_storage, new_size*sizeof *server_storage); 439 | memset(&server_storage[real_size], 0, (new_size-real_size)*sizeof server_storage[0]); 440 | servers = &server_storage[2]; /* making server[0] the first regular server */ 441 | real_size = new_size; 442 | if (size > nservers) nservers = size; 443 | } 444 | 445 | -------------------------------------------------------------------------------- /server.h: -------------------------------------------------------------------------------- 1 | #include 2 | #ifndef WINDOWS 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | #define ALG_HASH_VALID 1 9 | #define ALG_ROUNDROBIN 2 10 | #define ALG_WEIGHT 4 11 | #define ALG_PRIO 8 12 | #define ALG_HASH 16 13 | #define ALG_STUBBORN 32 14 | 15 | #define EMERGENCY_SERVER (-1) 16 | #define ABUSE_SERVER (-2) 17 | #define NO_SERVER (-3) 18 | 19 | #define BLACKLIST_TIME 30 /* how long to shun a server that is down */ 20 | #define WEIGHT_FACTOR 256 /* to make weight kick in earlier */ 21 | 22 | typedef struct { 23 | int status; /* last failed connection attempt */ 24 | int acl; /* which clients can use this server */ 25 | struct sockaddr_storage addr; 26 | uint8_t hwaddr[6]; 27 | int c; /* connections */ 28 | int weight; /* default 1 */ 29 | int prio; 30 | int maxc; /* max connections, soft limit */ 31 | int hard; /* max connections, hard limit */ 32 | uint64_t sx, rx; /* bytes sent, received */ 33 | } server; 34 | 35 | extern int nservers; /* number of servers */ 36 | extern server *servers; 37 | extern int current; 38 | extern int emerg_server; 39 | extern int abuse_server; 40 | extern int blacklist_time; 41 | extern int server_alg; 42 | 43 | extern char *e_server; 44 | extern char *a_server; 45 | 46 | extern void setaddress(int, char *, int, int); 47 | extern void blacklist_server(int); 48 | extern int unused_server_slot(int); 49 | extern int server_is_blacklisted(int); 50 | extern int server_is_unavailable(int); 51 | extern int server_by_roundrobin(void); 52 | extern int initial_server(int); 53 | extern int failover_server(int); 54 | extern int try_server(int, int); 55 | extern void expand_servertable(int); 56 | -------------------------------------------------------------------------------- /settings.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #ifndef WINDOWS 3 | #include 4 | #else 5 | #include 6 | #endif 7 | 8 | int foreground; 9 | int abort_on_error = 0; 10 | int multi_accept = 100; 11 | int tcp_fastclose = 0; 12 | int keepalive = 0; 13 | int udp = 0; 14 | int transparent = 0; 15 | -------------------------------------------------------------------------------- /settings.h: -------------------------------------------------------------------------------- 1 | extern int foreground; 2 | extern int abort_on_error; 3 | extern int multi_accept; 4 | extern int tcp_fastclose; 5 | extern int keepalive; 6 | extern int udp; 7 | extern int transparent; 8 | -------------------------------------------------------------------------------- /siag.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXwIBAAKBgQDKcE9kAiLqzJWQAImFRhC1e6wvfTtd1Gcy+eoP+Ngnqroijeon 3 | lV2vsNENsr+AMksRREeRFk4WP14MUZAB2p2iEy5J8jU31iUvFHswvWY6IEThwESP 4 | bt+3h2buJ6475UgQ3Ww2lJ6jTewBOEEZ/sRuqEmLy33bQEMOwFlSJcFsXwIDAQAB 5 | AoGBALxYDaUwT6hAu44E0e3LKFSRQbvVOdysd294EQrXLNSLBS6M9qPpVgbV7Upf 6 | Wcg+ApWH/0W/iNsuDaRMZj0zVqAClE9W9mcbIIKxAExj5kBiUSiVJjKiM/N6V/TH 7 | a9rqPpUsHkJ2YhN7VWgW8Fkd9l0Poe4eQaQfEYzv/reAHjLxAkEA6bBIB+OztkMv 8 | YgKRILwOxIaNUjeo8rYPc5hzQlfHAClr27eTJmKI5vFhjkiFsgZ97kjpBAhCUxv+ 9 | tq459txLhQJBAN3EO2vRgNOUp5SzGDE/UOp2D66jEtYeGF6Xwqao4WAuH2peVNTv 10 | vb5c8mPUL7XmaOEroTD6M5i2qPunuJI5g5MCQQCpVJLdJKGT8Brlafa/QYVx4g2F 11 | Bc/mDwYjPNRHvlL0Sw9cpih6J+wLa9zEvMgjt5CImUw/H7zve9mVhal6tyYBAkEA 12 | k0IAqyGcx+JIPSeHgvwmQOpQk4hZs7CcQgPID17I+VLnLXyiHBtuBYA2vC9j1we+ 13 | 0PZlvN4HcPqpzyGaIA7s3QJBAIBxJTwBYg/fzNYqeSIuIlVxN+Ao/Qgses7UJvIn 14 | qf7PhVAWrpuX1mo2Pi7QH6+F0Cmj8BEIrfIPe+kqnIBOHFQ= 15 | -----END RSA PRIVATE KEY----- 16 | -----BEGIN CERTIFICATE----- 17 | MIICfzCCAeigAwIBAgIBADANBgkqhkiG9w0BAQQFADA6MQswCQYDVQQGEwJTVjEZ 18 | MBcGA1UEChMQU2lhZyBPZmZpY2UsIEluYzEQMA4GA1UEAxMHc2lhZy5udTAeFw0w 19 | MzAzMTgwODQ4MTRaFw0zMDA4MDMwODQ4MTRaMDoxCzAJBgNVBAYTAlNWMRkwFwYD 20 | VQQKExBTaWFnIE9mZmljZSwgSW5jMRAwDgYDVQQDEwdzaWFnLm51MIGfMA0GCSqG 21 | SIb3DQEBAQUAA4GNADCBiQKBgQDKcE9kAiLqzJWQAImFRhC1e6wvfTtd1Gcy+eoP 22 | +NgnqroijeonlV2vsNENsr+AMksRREeRFk4WP14MUZAB2p2iEy5J8jU31iUvFHsw 23 | vWY6IEThwESPbt+3h2buJ6475UgQ3Ww2lJ6jTewBOEEZ/sRuqEmLy33bQEMOwFlS 24 | JcFsXwIDAQABo4GUMIGRMB0GA1UdDgQWBBTiAQBmjX5+5ki5ePNiF61jhmF5xDBi 25 | BgNVHSMEWzBZgBTiAQBmjX5+5ki5ePNiF61jhmF5xKE+pDwwOjELMAkGA1UEBhMC 26 | U1YxGTAXBgNVBAoTEFNpYWcgT2ZmaWNlLCBJbmMxEDAOBgNVBAMTB3NpYWcubnWC 27 | AQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOBgQC2nAZn6MdkmX7xQSP0 28 | 5Z3NeMx4BKHzfDZ0/gkcpKEfH4A45Nz6cReGy7WY4vkXM8BCN0X46ViKnfcXByrA 29 | MPuIEETTffZ1qzFvxolfDqJvsZGw7Tz7GvmbOBEk9itTyJ7h0AFiCr6JyoEUwniQ 30 | FD7bVtD5uFKUGksFrzBGdrBNyg== 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /ssl.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #ifdef HAVE_LIBSSL 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "ssl.h" 15 | #include "client.h" 16 | #include "conn.h" 17 | #include "pen.h" 18 | #include "diag.h" 19 | #include "memory.h" 20 | #include "server.h" 21 | 22 | char ssl_compat; 23 | char require_peer_cert; 24 | char ssl_protocol; 25 | char *certfile; 26 | char *keyfile; 27 | char *cacert_dir; 28 | char *cacert_file; 29 | char *ssl_sni_path; 30 | SSL_CTX *ssl_context = NULL; 31 | long ssl_options; 32 | char *ssl_ciphers; 33 | int ssl_session_id_context = 1; 34 | int ssl_client_renegotiation_interval = 3600; /* one hour, effectively disabled */ 35 | unsigned char ocsp_resp_data[OCSP_RESP_MAX]; 36 | long ocsp_resp_len = 0; 37 | char *ocsp_resp_file = NULL; 38 | 39 | static int ssl_verify_cb(int ok, X509_STORE_CTX *ctx) 40 | { 41 | char buffer[256]; 42 | X509 *cert = X509_STORE_CTX_get_current_cert(ctx); 43 | 44 | X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer)); 45 | if (ok) { 46 | char *subj = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); 47 | DEBUG(1, "SSL: Certificate OK: issuer = '%s'", buffer); 48 | DEBUG(1, "Subject: %s", subj); 49 | OPENSSL_free(subj); 50 | } else { 51 | switch (X509_STORE_CTX_get_error(ctx)) { 52 | case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: 53 | DEBUG(1, "SSL: Cert error: CA not known: %s", buffer); 54 | break; 55 | case X509_V_ERR_CERT_NOT_YET_VALID: 56 | DEBUG(1, "SSL: Cert error: Cert not yet valid: %s", 57 | buffer); 58 | break; 59 | case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: 60 | DEBUG(1, "SSL: Cert error: illegal \'not before\' field: %s", 61 | buffer); 62 | break; 63 | case X509_V_ERR_CERT_HAS_EXPIRED: 64 | DEBUG(1, "SSL: Cert error: Cert expired: %s", buffer); 65 | break; 66 | case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: 67 | DEBUG(1, "SSL: Cert error: invalid \'not after\' field: %s", 68 | buffer); 69 | break; 70 | default: 71 | DEBUG(1, "SSL: Cert error: unknown error %d in %s", 72 | X509_STORE_CTX_get_error(ctx), buffer); 73 | break; 74 | } 75 | } 76 | return ok; 77 | } 78 | 79 | static RSA *ssl_temp_rsa_cb(SSL *ssl, int export, int keylength) 80 | { 81 | static RSA *rsa = NULL; 82 | 83 | if (rsa == NULL) { 84 | RSA *rsa = RSA_new(); 85 | BIGNUM *bn = BN_new(); 86 | if (rsa == NULL || 87 | bn == NULL || 88 | !BN_set_word(bn, RSA_F4) || 89 | !RSA_generate_key_ex(rsa, 512, bn, NULL)) { 90 | BN_free(bn); 91 | RSA_free(rsa); rsa = NULL; 92 | } 93 | } 94 | return rsa; 95 | } 96 | 97 | static void ssl_info_cb(const SSL *ssl, int where, int ret) 98 | { 99 | #if defined SSL3_ST_SR_CLNT_HELLO_A || defined SSL23_ST_SR_CLNT_HELLO_A 100 | int st = SSL_get_state(ssl); 101 | #endif 102 | const char *state = SSL_state_string_long(ssl); 103 | const char *type = SSL_alert_type_string_long(ret); 104 | const char *desc = SSL_alert_desc_string_long(ret); 105 | connection *conn = SSL_get_app_data(ssl); 106 | int renegotiating = 0; 107 | DEBUG(3, "ssl_info_cb(ssl=%p, where=%d, ret=%d)", ssl, where, ret); 108 | if (where & SSL_CB_LOOP) DEBUG(3, "\tSSL_CB_LOOP"); 109 | if (where & SSL_CB_EXIT) DEBUG(3, "\tSSL_CB_EXIT"); 110 | if (where & SSL_CB_READ) DEBUG(3, "\tSSL_CB_READ"); 111 | if (where & SSL_CB_WRITE) DEBUG(3, "\tSSL_CB_WRITE"); 112 | if (where & SSL_CB_ALERT) DEBUG(3, "\tSSL_CB_ALERT"); 113 | if (where & SSL_CB_READ_ALERT) DEBUG(3, "\tSSL_CB_READ_ALERT"); 114 | if (where & SSL_CB_WRITE_ALERT) DEBUG(3, "\tSSL_CB_WRITE_ALERT"); 115 | if (where & SSL_CB_ACCEPT_LOOP) DEBUG(3, "\tSSL_CB_ACCEPT_LOOP"); 116 | if (where & SSL_CB_ACCEPT_EXIT) DEBUG(3, "\tSSL_CB_ACCEPT_EXIT"); 117 | if (where & SSL_CB_CONNECT_LOOP) DEBUG(3, "\tSSL_CB_CONNECT_LOOP"); 118 | if (where & SSL_CB_CONNECT_EXIT) DEBUG(3, "\tSSL_CB_CONNECT_EXIT"); 119 | if (where & SSL_CB_HANDSHAKE_START) DEBUG(3, "\tSSL_CB_HANDSHAKE_START"); 120 | if (where & SSL_CB_HANDSHAKE_DONE) DEBUG(3, "\tSSL_CB_HANDSHAKE_DONE"); 121 | DEBUG(3, "SSL state = %s", state); 122 | DEBUG(3, "Alert type = %s", type); 123 | DEBUG(3, "Alert description = %s", desc); 124 | #ifdef SSL3_ST_SR_CLNT_HELLO_A 125 | if (st == SSL3_ST_SR_CLNT_HELLO_A) { 126 | DEBUG(3, "\tSSL3_ST_SR_CLNT_HELLO_A"); 127 | renegotiating = 1; 128 | } 129 | #endif 130 | #ifdef SSL23_ST_SR_CLNT_HELLO_A 131 | if (st == SSL23_ST_SR_CLNT_HELLO_A) { 132 | DEBUG(3, "\tSSL23_ST_SR_CLNT_HELLO_A"); 133 | renegotiating = 1; 134 | } 135 | #endif 136 | if (conn == NULL) { 137 | debug("Whoops, no conn info"); 138 | } else { 139 | DEBUG(3, "Connection in state %d from client %d to server %d", 140 | conn->state, conn->client, conn->server); 141 | if (renegotiating) { 142 | int reneg_time = now-conn->reneg; 143 | conn->reneg = now; 144 | DEBUG(3, "Client asks for renegotiation"); 145 | DEBUG(3, "Last time was %d seconds ago", reneg_time); 146 | if (reneg_time < ssl_client_renegotiation_interval) { 147 | debug("That's more often than we care for"); 148 | conn->state = CS_CLOSED; 149 | } 150 | } 151 | } 152 | } 153 | 154 | static struct sni { 155 | char *name; 156 | SSL_CTX *ssl_context; 157 | unsigned char ocsp_resp_data[OCSP_RESP_MAX]; 158 | int ocsp_resp_len; 159 | time_t ocsp_resp_time; 160 | struct sni *next; 161 | } *sni_list; 162 | 163 | static SSL_CTX *ssl_create_context(char *keyfile, char *certfile, 164 | char *cacert_dir, char *cacert_file); 165 | 166 | static struct sni *lookup_sni(const char *name) 167 | { 168 | struct sni *s; 169 | for (s = sni_list; s; s = s->next) 170 | if (!strcmp(s->name, name)) break; 171 | 172 | if (s == NULL) { 173 | char keyfile[1024], certfile[1024], cacert_file[1024]; 174 | s = pen_malloc(sizeof *s); 175 | s->name = pen_strdup(name); 176 | s->next = sni_list; 177 | sni_list = s; 178 | snprintf(keyfile, sizeof keyfile, "%s/%s.key", ssl_sni_path, name); 179 | snprintf(certfile, sizeof certfile, "%s/%s.crt", ssl_sni_path, name); 180 | snprintf(cacert_file, sizeof cacert_file, "%s/%s.ca", ssl_sni_path, name); 181 | s->ocsp_resp_len = 0; 182 | s->ocsp_resp_time = 0; /* never */ 183 | s->ssl_context = ssl_create_context(keyfile, certfile, NULL, cacert_file); 184 | } 185 | return s; 186 | } 187 | 188 | static long read_ocsp(const char *fn, unsigned char *data) 189 | { 190 | int f = open(fn, O_RDONLY); 191 | long len = 0; 192 | DEBUG(3, "Read ocsp response from '%s'", fn); 193 | if (f == -1) { 194 | DEBUG(3, "Can't read file"); 195 | } else { 196 | len = read(f, data, OCSP_RESP_MAX); 197 | DEBUG(3, "Read %ld bytes of ocsp response", len); 198 | close(f); 199 | } 200 | if (len < 0) len = 0; 201 | return len; 202 | } 203 | 204 | static int ssl_stapling_cb(SSL *ssl, void *p) 205 | { 206 | connection *conn = SSL_get_app_data(ssl); 207 | unsigned char *data, *ocsp_resp_copy; 208 | long len = 0; 209 | 210 | if (conn == NULL) { 211 | debug("Whoops, no conn info"); 212 | return SSL_TLSEXT_ERR_ALERT_FATAL; 213 | } else { 214 | DEBUG(3, "ssl_stapling_cb() called for connection from client %d to server %d", 215 | conn->client, conn->server); 216 | } 217 | if (SSL_get_SSL_CTX(ssl) != ssl_context) { 218 | const char *n; 219 | char ocsp_file[1024]; 220 | struct sni *s; 221 | DEBUG(3, "stapling for sni context"); 222 | n = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 223 | if (n == NULL) { 224 | DEBUG(3, "SNI hostname null, giving up"); 225 | return SSL_TLSEXT_ERR_NOACK; 226 | } 227 | s = lookup_sni(n); 228 | if (now-s->ocsp_resp_time > 3600) { /* seems about right */ 229 | snprintf(ocsp_file, sizeof ocsp_file, "%s/%s.ocsp", ssl_sni_path, n); 230 | s->ocsp_resp_len = read_ocsp(ocsp_file, s->ocsp_resp_data); 231 | s->ocsp_resp_time = now; 232 | } 233 | len = s->ocsp_resp_len; 234 | data = s->ocsp_resp_data; 235 | } else { 236 | DEBUG(3, "stapling for default context"); 237 | if (ocsp_resp_file) { 238 | ocsp_resp_len = read_ocsp(ocsp_resp_file, ocsp_resp_data); 239 | free(ocsp_resp_file); 240 | ocsp_resp_file = NULL; 241 | } 242 | len = ocsp_resp_len; 243 | data = ocsp_resp_data; 244 | } 245 | if (len == 0) { 246 | DEBUG(3, "No ocsp data"); 247 | return SSL_TLSEXT_ERR_NOACK; 248 | } 249 | ocsp_resp_copy = pen_malloc(len); 250 | memcpy(ocsp_resp_copy, data, len); 251 | SSL_set_tlsext_status_ocsp_resp(ssl, ocsp_resp_copy, len); 252 | return SSL_TLSEXT_ERR_OK; 253 | } 254 | 255 | static int ssl_sni_cb(SSL *ssl, int *foo, void *arg) 256 | { 257 | const char *n; 258 | struct sni *s; 259 | 260 | n = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); 261 | if (n == NULL) { 262 | DEBUG(3, "SNI hostname null, giving up"); 263 | return SSL_TLSEXT_ERR_NOACK; 264 | } 265 | DEBUG(3, "ssl_sni_cb() => name = '%s'", n); 266 | if (ssl_sni_path == NULL) { 267 | DEBUG(3, "ssl_sni_path not set, giving up"); 268 | return SSL_TLSEXT_ERR_NOACK; 269 | } 270 | s = lookup_sni(n); 271 | if (s->ssl_context == NULL) return SSL_TLSEXT_ERR_NOACK; 272 | SSL_set_SSL_CTX(ssl, s->ssl_context); 273 | return SSL_TLSEXT_ERR_OK; 274 | } 275 | 276 | static SSL_CTX *ssl_create_context(char *keyfile, char *certfile, 277 | char *cacert_dir, char *cacert_file) 278 | { 279 | int n, err; 280 | SSL_CTX *ssl_context; 281 | 282 | if (certfile == NULL || *certfile == 0) { 283 | debug("SSL: No cert file specified in config file!"); 284 | debug("The server MUST have a certificate!"); 285 | return NULL; 286 | } 287 | if (keyfile == NULL || *keyfile == 0) 288 | keyfile = certfile; 289 | if (cacert_dir != NULL && *cacert_dir == 0) 290 | cacert_dir = NULL; 291 | if (cacert_file != NULL && *cacert_file == 0) 292 | cacert_file = NULL; 293 | 294 | ssl_context = SSL_CTX_new(SSLv23_method()); 295 | if (ssl_context == NULL) { 296 | err = ERR_get_error(); 297 | debug("SSL: Error allocating context: %s", 298 | ERR_error_string(err, NULL)); 299 | return NULL; 300 | } 301 | switch (ssl_protocol) { 302 | #ifndef OPENSSL_NO_SSL3 303 | case SRV_SSL_V3: 304 | ssl_options |= SSL_OP_NO_SSLv2; 305 | break; 306 | #endif 307 | case SRV_SSL_TLS1: 308 | ssl_options |= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3; 309 | break; 310 | } 311 | DEBUG(1, "ssl_options = 0x%lx", ssl_options); 312 | if (ssl_options) { 313 | SSL_CTX_set_options(ssl_context, ssl_options); 314 | } 315 | if (ssl_compat) { 316 | SSL_CTX_set_options(ssl_context, SSL_OP_ALL); 317 | } 318 | DEBUG(1, "ssl_ciphers = '%s'", ssl_ciphers); 319 | if (ssl_ciphers) { 320 | n = SSL_CTX_set_cipher_list(ssl_context, ssl_ciphers); 321 | if (n == 0) { 322 | err = ERR_get_error(); 323 | DEBUG(3, "SSL_CTX_set_cipher_list(ssl_context, %s) returns %d (%s)", 324 | ssl_ciphers, n, err); 325 | } 326 | } 327 | 328 | if (!SSL_CTX_use_certificate_file(ssl_context, certfile, 329 | SSL_FILETYPE_PEM)) { 330 | err = ERR_get_error(); 331 | debug("SSL: error reading certificate from file %s: %s", 332 | certfile, ERR_error_string(err, NULL)); 333 | return NULL; 334 | } 335 | if (!SSL_CTX_use_PrivateKey_file(ssl_context, keyfile, 336 | SSL_FILETYPE_PEM)) { 337 | err = ERR_get_error(); 338 | debug("SSL: error reading private key from file %s: %s", 339 | keyfile, ERR_error_string(err, NULL)); 340 | return NULL; 341 | } 342 | if (!SSL_CTX_check_private_key(ssl_context)) { 343 | debug("SSL: Private key does not match public key in cert!"); 344 | return NULL; 345 | } 346 | 347 | if (cacert_dir != NULL || cacert_file != NULL) { 348 | if (!SSL_CTX_load_verify_locations(ssl_context, 349 | cacert_file, cacert_dir)) { 350 | err = ERR_get_error(); 351 | debug("SSL: Error error setting CA cert locations: %s", 352 | ERR_error_string(err, NULL)); 353 | cacert_file = cacert_dir = NULL; 354 | } 355 | } 356 | if (cacert_dir == NULL && cacert_file == NULL) { /* no verify locations loaded */ 357 | debug("SSL: No verify locations, trying default"); 358 | if (!SSL_CTX_set_default_verify_paths(ssl_context)) { 359 | err = ERR_get_error(); 360 | debug("SSL: Error error setting default CA cert location: %s", 361 | ERR_error_string(err, NULL)); 362 | debug("continuing anyway..."); 363 | } 364 | } 365 | SSL_CTX_set_tmp_rsa_callback(ssl_context, ssl_temp_rsa_cb); 366 | SSL_CTX_set_info_callback(ssl_context, ssl_info_cb); 367 | SSL_CTX_set_tlsext_status_cb(ssl_context, ssl_stapling_cb); 368 | SSL_CTX_set_tlsext_servername_callback(ssl_context, ssl_sni_cb); 369 | if (require_peer_cert) { 370 | SSL_CTX_set_verify(ssl_context, 371 | SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 372 | ssl_verify_cb); 373 | } else { 374 | SSL_CTX_set_verify(ssl_context, 375 | SSL_VERIFY_NONE, 376 | ssl_verify_cb); 377 | } 378 | 379 | SSL_CTX_set_client_CA_list(ssl_context, 380 | SSL_load_client_CA_file(certfile)); 381 | 382 | /* permit large writes to be split up in several records */ 383 | SSL_CTX_set_mode(ssl_context, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); 384 | 385 | DEBUG(3, "SSL_CTX_get_session_cache_mode() returns %d", 386 | SSL_CTX_get_session_cache_mode(ssl_context)); 387 | SSL_CTX_set_session_cache_mode(ssl_context, SSL_SESS_CACHE_SERVER); 388 | 389 | #if defined(HAVE_EC_KEY) && defined(NID_X9_62_prime256v1) 390 | EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); 391 | if (ecdh == NULL) { 392 | debug("EC_KEY_new_by_curve_name failure"); 393 | } else { 394 | if (SSL_CTX_set_tmp_ecdh(ssl_context, ecdh) != 1) { 395 | debug("SSL_CTX_set_tmp_ecdh failure"); 396 | } else { 397 | DEBUG(1, "ECDH Initialized with NIST P-256"); 398 | } 399 | EC_KEY_free(ecdh); 400 | } 401 | #endif 402 | 403 | SSL_CTX_set_session_id_context(ssl_context, (void *)&ssl_session_id_context, 404 | sizeof ssl_session_id_context); 405 | 406 | return ssl_context; 407 | } 408 | 409 | int ssl_init(void) 410 | { 411 | SSL_load_error_strings(); 412 | SSLeay_add_ssl_algorithms(); 413 | ssl_context = ssl_create_context(keyfile, certfile, cacert_dir, cacert_file); 414 | if (ssl_context == NULL) { 415 | error("Unable to create default context"); 416 | } 417 | 418 | return 0; 419 | } 420 | 421 | #endif /* HAVE_LIBSSL */ 422 | -------------------------------------------------------------------------------- /ssl.h: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_LIBSSL 2 | #include 3 | #include 4 | 5 | #define SRV_SSL_V23 0 6 | #define SRV_SSL_V2 1 7 | #define SRV_SSL_V3 2 8 | #define SRV_SSL_TLS1 3 9 | 10 | #define OCSP_RESP_MAX 10000 11 | 12 | extern char ssl_compat; 13 | extern char require_peer_cert; 14 | extern char ssl_protocol; 15 | extern char *certfile; 16 | extern char *keyfile; 17 | extern char *cacert_dir; 18 | extern char *cacert_file; 19 | extern char *ssl_sni_path; 20 | extern SSL_CTX *ssl_context; 21 | extern long ssl_options; 22 | extern char *ssl_ciphers; 23 | extern int ssl_session_id_context; 24 | extern int ssl_client_renegotiation_interval; 25 | extern unsigned char ocsp_resp_data[OCSP_RESP_MAX]; 26 | extern long ocsp_resp_len; 27 | extern char *ocsp_resp_file; 28 | 29 | extern int ssl_init(void); 30 | 31 | #endif /* HAVE_LIBSSL */ 32 | -------------------------------------------------------------------------------- /testsuite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Some assembly required - see bottom of script. 4 | 5 | # We need two local ipv4 addresses, these should be available in a virtualbox vm 6 | IP1=127.0.0.1 7 | IP2=10.0.2.15 8 | # Otherwise put them in testsuite.cfg 9 | if test -s ./testsuite.cfg; then 10 | . ./testsuite.cfg 11 | fi 12 | 13 | PID=./autotest.pid 14 | CTL=./autotest.ctl 15 | 16 | DIG="dig +short +time=3 +retry=0" 17 | 18 | fail() 19 | { 20 | echo "$1" 1>&2 21 | exit 1 22 | } 23 | 24 | isrunning() 25 | { 26 | ps -ef|grep -v grep|grep "./pen " > /dev/null 2>&1 27 | } 28 | 29 | stop_pen() 30 | { 31 | # Make sure pen is not running 32 | killall pen > /dev/null 2>&1 33 | sleep 1 34 | if isrunning; then 35 | fail "Pen is running" 36 | fi 37 | 38 | # Make sure pid file and control sockets don't exist 39 | rm -f $PID $CTL 40 | if test -f $PID; then 41 | fail "PID file $PID exists" 42 | fi 43 | if test -f $CTL; then 44 | fail "Control socket $CTL exists" 45 | fi 46 | } 47 | 48 | start_pen() 49 | { 50 | echo "Command line: ./pen -p $PID -C $CTL -X $1" 51 | ./pen -p $PID -C $CTL -X $1 52 | if ! isrunning; then 53 | fail "Pen did not start" 54 | fi 55 | } 56 | 57 | penctl() 58 | { 59 | echo "./penctl $CTL $@" 60 | ./penctl $CTL $@ > /dev/null 61 | } 62 | 63 | # Usage: sample host command 64 | sample() 65 | { 66 | S=`ssh $1 "$2"` 67 | echo "$S" 68 | } 69 | 70 | check_result() 71 | { 72 | echo "$1: expected '$2', got '$3'" 73 | if test -z "$3" -o "$2" != "$3"; then 74 | fail "Wrong result" 75 | fi 76 | } 77 | 78 | check_different() 79 | { 80 | echo "$1: expected not '$2', got '$3'" 81 | if test -z "$3" -o "$2" = "$3"; then 82 | fail "Wrong result" 83 | fi 84 | } 85 | 86 | echo "Test run: `date`" 87 | echo 88 | echo "Testing TCP with IP-based client tracking" 89 | stop_pen 90 | start_pen "8080 127.0.0.1:100 127.0.0.1:101" 91 | 92 | # Try fetching the web server hostname 93 | H1a=`curl -s http://$IP1:8080/` 94 | check_different "First result" "" "$H1a" 95 | H1b=`curl -s http://$IP1:8080/` 96 | # We are tracking client IP so expect this to be identical" 97 | check_result "Second result" "$H1a" "$H1b" 98 | # But not this 99 | H2a=`curl -s http://$IP2:8080/` 100 | check_different "Third result" "$H1a" "$H2a" 101 | H2b=`curl -s http://$IP2:8080/` 102 | check_result "Fourth result" "$H2a" "$H2b" 103 | echo "Success" 104 | echo 105 | 106 | echo "Testing TCP without client tracking" 107 | stop_pen 108 | start_pen "-r 8080 127.0.0.1:100 127.0.0.1:101" 109 | 110 | H1a=`curl -s http://$IP1:8080/` 111 | check_different "First result" "" "$H1a" 112 | H1b=`curl -s http://$IP1:8080/` 113 | check_different "Second result" "$H1a" "$H1b" 114 | H2a=`curl -s http://$IP2:8080/` 115 | check_result "Third result" "$H1a" "$H2a" 116 | H2b=`curl -s http://$IP2:8080/` 117 | check_different "Fourth result" "$H2a" "$H2b" 118 | echo Success 119 | echo 120 | 121 | echo "Testing UDP with IP-based client tracking" 122 | stop_pen 123 | start_pen "-U 8080 $IP1:53 $IP2:53" 124 | 125 | # Try fetching the web server hostname 126 | H1a=`$DIG @$IP1 -p 8080 example.com` 127 | check_different "First result" "" "$H1a" 128 | # We are tracking client IP so expect this to be identical" 129 | H1b=`$DIG @$IP1 -p 8080 example.com` 130 | check_result "Second result" "$H1a" "$H1b" 131 | # But not this 132 | H2a=`$DIG @$IP2 -p 8080 example.com` 133 | check_different "Third result" "$H1a" "$H2a" 134 | H2b=`$DIG @$IP2 -p 8080 example.com` 135 | check_result "Fourth result" "$H2a" "$H2b" 136 | echo Success 137 | echo 138 | 139 | echo "Testing UDP without client tracking" 140 | stop_pen 141 | start_pen "-Ur 8080 $IP1:53 $IP2:53" 142 | 143 | # Try fetching the web server hostname 144 | H1a=`$DIG @$IP1 -p 8080 example.com` 145 | check_different "First result" "" "$H1a" 146 | H1b=`$DIG @$IP1 -p 8080 example.com` 147 | check_different "Second result" "$H1a" "$H1b" 148 | H2a=`$DIG @$IP2 -p 8080 example.com` 149 | check_result "Third result" "$H1a" "$H2a" 150 | H2b=`$DIG @$IP2 -p 8080 example.com` 151 | check_different "Fourth result" "$H2b" "$H2a" 152 | echo Success 153 | echo 154 | 155 | echo "Testing failover with UDP (see issue #19)" 156 | stop_pen 157 | for p in 10000 10001 10002; do 158 | ./pen -U $p $IP1:53 159 | H=`$DIG @$IP1 -p $p example.com` 160 | check_result "Result from target $p" "1.1.1.1" "$H" 161 | done 162 | echo Targets prepared 163 | 164 | start_pen "-Ur 5353 127.0.0.1:10000 127.0.0.1:10001 127.0.0.1:10002" 165 | echo Pen started 166 | H=`$DIG @127.0.0.1 -p 5353 example.com` 167 | check_result "First result from load balanced service" "1.1.1.1" "$H" 168 | echo Blacklist one server 169 | penctl server 1 blacklist 30 170 | H=`$DIG @127.0.0.1 -p 5353 example.com` 171 | check_result "Second result from load balanced service" "1.1.1.1" "$H" 172 | echo Blacklist another server 173 | penctl server 2 blacklist 30 174 | H=`$DIG @127.0.0.1 -p 5353 example.com` 175 | check_result "Third result from load balanced service" "1.1.1.1" "$H" 176 | echo Blacklist third server, expecting dig to fail 177 | penctl server 0 blacklist 30 178 | H=`$DIG @127.0.0.1 -p 5353 example.com` 179 | E=$? 180 | echo "dig exit code: $E" 181 | echo "Fourth result: $H" 182 | if test "$E" != "9"; then 183 | fail "Wrong dig exit code" 184 | fi 185 | echo Whitelist one server 186 | penctl server 0 blacklist 0 187 | H=`$DIG @127.0.0.1 -p 5353 example.com` 188 | check_result "Fifth result from load balanced service" "1.1.1.1" "$H" 189 | echo "Success" 190 | echo 191 | 192 | echo "Testing emergency server" 193 | stop_pen 194 | start_pen "-r -e 127.0.0.1:102 10000 127.0.0.1:100 127.0.0.1:101:0:0:101" 195 | # Curl repeatedly to verify that roundrobin works: 196 | H1=`curl -s http://localhost:10000/` 197 | check_different "First result" "" "$H1" 198 | H2=`curl -s http://localhost:10000/` 199 | check_different "Second result" "$H1" "$H2" 200 | echo Blacklist one of the servers so that all replies come from the other server 201 | penctl server 0 blacklist 100 202 | H1=`curl -s http://localhost:10000/` 203 | check_different "Third result" "" "$H1" 204 | H2=`curl -s http://localhost:10000/` 205 | check_result "Fourth result" "$H1" "$H2" 206 | echo Blacklist the other server. Now all replies should come from the emergency server 207 | penctl server 1 blacklist 100 208 | H1=`curl -s http://localhost:10000/` 209 | check_result "Fifth result" "Emergency" "$H1" 210 | echo Whitelist one of the servers 211 | penctl server 0 blacklist 0 212 | H1=`curl -s http://localhost:10000/` 213 | check_different "Sixth result" "" "$H1" 214 | H2=`curl -s http://localhost:10000/` 215 | check_result "Seventh result" "$H1" "$H2" 216 | echo Whitelist the other server 217 | penctl server 1 blacklist 0 218 | H1=`curl -s http://localhost:10000/` 219 | check_different "Eighth result" "" "$H1" 220 | H2=`curl -s http://localhost:10000/` 221 | check_different "Ninth result" "$H1" "$H2" 222 | echo Success 223 | echo 224 | 225 | echo "Testing abuse server" 226 | stop_pen 227 | start_pen "-B 127.0.0.1:103 10000 127.0.0.1:100" 228 | penctl acl 1 permit $IP1 229 | penctl client_acl 1 230 | H=`curl -s http://$IP1:10000/` 231 | check_different "First result" "Abuse" "$H" 232 | H=`curl -s http://$IP2:10000/` 233 | check_result "Second result" "Abuse" "$H" 234 | penctl client_acl 0 235 | H=`curl -s http://$IP2:10000/` 236 | check_different "Third result" "Abuse" "$H" 237 | echo "Blocking access to single regular server will result in dropped connection" 238 | penctl server 0 acl 1 239 | H=`curl -s http://$IP1:10000/` 240 | check_different "Fourth result" "Abuse" "$H" 241 | H=`curl -s http://$IP2:10000/` 242 | echo "curl exit code: $?" 243 | echo "Fifth result: expected '', got '$H'" 244 | if test "$H" != ""; then 245 | fail "Wrong result" 246 | fi 247 | echo Success 248 | 249 | echo 250 | echo "Testing SSL termination" 251 | stop_pen 252 | start_pen "-E siag.pem 1443 127.0.0.1:100" 253 | H=`curl -sk https://127.0.0.1:1443/` 254 | check_result "First result" "100" "$H" 255 | echo Success 256 | 257 | # Absurdly, glibc requires an IPv6 address on a non-loopback interface 258 | # in order for getaddrinfo to handle IPv6. This can be accomplished thus: 259 | # /sbin/ifconfig eth1 add ::2/128 260 | echo 261 | echo "Testing IPv6 to IPv4 conversion" 262 | stop_pen 263 | start_pen ":::10000 127.0.0.1:100" 264 | H=`curl -sg6 http://[::1]:10000/` 265 | check_result "First result" "100" "$H" 266 | echo Success 267 | 268 | echo 269 | echo "Testing IPv4 to IPv6 conversion" 270 | stop_pen 271 | start_pen "127.0.0.1:10000 [::1]:100" 272 | H=`curl -s4 http://127.0.0.1:10000/` 273 | check_result "First result" "100" "$H" 274 | echo Success 275 | 276 | echo 277 | echo "Testing signal handling" 278 | stop_pen 279 | echo "server 0 address 127.0.0.1 port 100" > autotest.cfg 280 | start_pen "10000 -F autotest.cfg" 281 | H=`curl -s http://127.0.0.1:10000/` 282 | check_result "First result" "100" "$H" 283 | echo "server 0 address 127.0.0.1 port 101" > autotest.cfg 284 | kill -HUP `cat $PID` 285 | H=`curl -s http://127.0.0.1:10000/` 286 | check_result "Second result" "101" "$H" 287 | echo Success 288 | 289 | # The next two tests, transparent reverse proxy and direct server return, require 290 | # extensive setting up. The are therefore conditioned on having separate config 291 | # in testsuite.cfg. It might look something like this (only not commented out): 292 | 293 | #TRP_PEN=192.168.1.2 294 | #TRP_BACK1=192.168.2.2 295 | #TRP_BACK2=192.168.2.3 296 | #TRP_MYIP=192.168.1.1 297 | 298 | #DSR_PEN=192.168.1.3 299 | #DSR_IP=192.168.2.10 300 | #DSR_BACK1=$TRP_BACK1 301 | #DSR_BACK2=$TRP_BACK2 302 | #DSR_MYIP=$TRP_MYIP 303 | #TARPIT_IP=192.168.2.11 304 | 305 | if test ! -z "$TRP_PEN"; then 306 | echo 307 | echo "Testing Transparent Reverse Proxy, TCP" 308 | stop_pen 309 | ssh root@$TRP_PEN "cd Git/pen && git pull && make && killall pen ; ./pen -r -O transparent 80 $TRP_BACK1 $TRP_BACK2" 310 | H=`curl -s http://$TRP_PEN/cgi-bin/remote_addr` 311 | check_result "Transparent result" "$TRP_MYIP" "$H" 312 | ssh root@$TRP_PEN "cd Git/pen && killall pen ; ./pen -r 80 $TRP_BACK1 $TRP_BACK2" 313 | H=`curl -s http://$TRP_PEN/cgi-bin/remote_addr` 314 | check_different "Nontransparent result" "$TRP_MYIP" "$H" 315 | echo Success 316 | 317 | echo 318 | echo "Testing Transparent Reverse Proxy, UDP" 319 | stop_pen 320 | ssh root@$TRP_PEN "cd Git/pen && ./pen -r -O transparent -U 53 $TRP_BACK1 $TRP_BACK2" 321 | H=`dig +short @$TRP_PEN -x 127.0.0.1` 322 | check_result "Transparent result" "localhost." "$H" 323 | echo Success 324 | fi 325 | 326 | if test ! -z "$DSR_PEN"; then 327 | echo 328 | echo "Testing Direct Server Return and Tarpit" 329 | stop_pen 330 | ssh root@$DSR_PEN "cd Git/pen && git pull && make && killall pen ; ./pen -r -O 'acl 1 permit $TARPIT_IP' -O 'tarpit_acl 1' -O 'dsr_if eth1' $DSR_IP:80 $DSR_BACK1 $DSR_BACK2" 331 | echo 332 | H=`nmap -p 80 $TARPIT_IP|grep ^80/tcp|awk '{print $2}'` 333 | check_result "Nmap result from legitimate address" "open" "$H" 334 | H=`curl -s http://$DSR_IP/cgi-bin/remote_addr` 335 | check_result "Curl result from legitimate address" "$DSR_MYIP" "$H" 336 | H=`nmap -p 80 $TARPIT_IP|grep ^80/tcp|awk '{print $2}'` 337 | check_result "Nmap result from tarpitted address" "open" "$H" 338 | H=`curl -s -m 3 http://$TARPIT_IP/cgi-bin/remote_addr` 339 | E=$? 340 | echo "Curl exit code: $E" 341 | echo "Curl result from tarpitted address: $H" 342 | if test "$E" = "0"; then 343 | fail "Wrong curl exit code" 344 | fi 345 | echo Success 346 | fi 347 | 348 | stop_pen 349 | 350 | exit 0 351 | 352 | # Stuff that needs to be prepared for this script to work: 353 | 354 | # Apache with four virtual hosts on different ports: 355 | Listen 100 356 | Listen 101 357 | Listen 102 358 | Listen 103 359 | 360 | DocumentRoot /var/www/html/100 361 | 362 | 363 | DocumentRoot /var/www/html/101 364 | 365 | 366 | DocumentRoot /var/www/html/102 367 | 368 | 369 | DocumentRoot /var/www/html/103 370 | 371 | 372 | # find /var/www/html/??? -name index.html -exec echo -n {} ": " \; -exec cat {} \; 373 | /var/www/html/100/index.html : 100 374 | /var/www/html/101/index.html : 101 375 | /var/www/html/102/index.html : Emergency 376 | /var/www/html/103/index.html : Abuse 377 | 378 | # And a bind with split horizon, serving only example.com: 379 | 380 | acl view1 { 381 | 127.0.0.0/8; 382 | }; 383 | 384 | acl view2 { 385 | 10.0.0.0/8; 386 | }; 387 | 388 | view "view1" { 389 | match-clients { view1; }; 390 | zone "example.com" IN { 391 | type master; 392 | file "/etc/bind/db.example.com.view1"; 393 | }; 394 | }; 395 | 396 | view "view2" { 397 | match-clients { view2; }; 398 | zone "example.com" IN { 399 | type master; 400 | file "/etc/bind/db.example.com.view2"; 401 | }; 402 | }; 403 | 404 | # cat db.example.com.view1 405 | $TTL 3h 406 | @ IN SOA localhost. root.localhost. ( 407 | 1 408 | 604800 409 | 86400 410 | 2419200 411 | 604800 ) 412 | @ IN NS localhost. 413 | @ IN A 1.1.1.1 414 | 415 | # cat db.example.com.view2 416 | $TTL 3h 417 | @ IN SOA localhost. root.localhost. ( 418 | 1 419 | 604800 420 | 86400 421 | 2419200 422 | 604800 ) 423 | @ IN NS localhost. 424 | @ IN A 2.2.2.2 425 | 426 | -------------------------------------------------------------------------------- /windows.c: -------------------------------------------------------------------------------- 1 | #include "diag.h" 2 | #include "pen.h" 3 | #include "windows.h" 4 | 5 | int sigaction(int signum, const struct sigaction *act, 6 | struct sigaction *oldact) 7 | { 8 | return 0; 9 | } 10 | 11 | uid_t getuid(void) 12 | { 13 | return 0; 14 | } 15 | 16 | 17 | int inet_aton(const char *cp, struct in_addr *addr) 18 | { 19 | addr->s_addr = inet_addr(cp); 20 | return (addr->s_addr == INADDR_NONE) ? 0 : 1; 21 | } 22 | 23 | 24 | void make_nonblocking(int fd) 25 | { 26 | int i; 27 | u_long mode = 1; 28 | if ((i = ioctlsocket(fd, FIONBIO, &mode)) != NO_ERROR) 29 | error("Can't ioctlsocket, error = %d", i); 30 | } 31 | 32 | static WSADATA wsaData; 33 | static int ws_started = 0; 34 | 35 | int start_winsock(void) 36 | { 37 | int n; 38 | DEBUG(1, "start_winsock()"); 39 | if (!ws_started) { 40 | n = WSAStartup(MAKEWORD(2, 2), &wsaData); 41 | if (n != NO_ERROR) { 42 | error("Error at WSAStartup() [%d]", WSAGetLastError()); 43 | } else { 44 | DEBUG(2, "Winsock started"); 45 | ws_started = 1; 46 | } 47 | } 48 | return ws_started; 49 | } 50 | 51 | void stop_winsock(void) 52 | { 53 | WSACleanup(); 54 | ws_started = 0; 55 | } 56 | 57 | 58 | 59 | static SERVICE_STATUS ServiceStatus; 60 | static SERVICE_STATUS_HANDLE ServiceStatusHandle; 61 | 62 | static VOID WINAPI ServiceCtrlHandler (DWORD opcode); 63 | static VOID WINAPI ServiceStart (DWORD argc, LPTSTR *argv); 64 | static DWORD ServiceInitialization (DWORD argc, LPTSTR *argv, DWORD *specificError); 65 | 66 | static void DeletePenService(SC_HANDLE schSCManager, char *service_name) 67 | { 68 | SC_HANDLE schService; 69 | 70 | schService = OpenService(schSCManager, service_name, SERVICE_ALL_ACCESS); 71 | if (schService == NULL) { 72 | debug("Can't open service (%d)", (int)GetLastError()); 73 | return; 74 | } 75 | if (!DeleteService(schService)) { 76 | debug("Can't delete service (%d)", (int)GetLastError()); 77 | } 78 | CloseServiceHandle(schService); 79 | } 80 | 81 | int delete_service(char *service_name) 82 | { 83 | SC_HANDLE schSCManager; 84 | 85 | debug("delete_service()", 0); 86 | 87 | // Open a handle to the SC Manager database. 88 | 89 | schSCManager = OpenSCManager( 90 | NULL, // local machine 91 | NULL, // ServicesActive database 92 | SC_MANAGER_ALL_ACCESS); // full access rights 93 | 94 | if (NULL == schSCManager) { 95 | debug("OpenSCManager failed (%d)", (int)GetLastError()); 96 | return 0; 97 | } 98 | 99 | DeletePenService(schSCManager, service_name); 100 | return 1; 101 | } 102 | 103 | static BOOL CreatePenService(SC_HANDLE schSCManager, char *service_name, char *display_name) 104 | { 105 | TCHAR szPath[MAX_PATH]; 106 | SC_HANDLE schService; 107 | 108 | if( !GetModuleFileName( NULL, szPath, MAX_PATH ) ) { 109 | debug("GetModuleFileName failed (%d)", (int)GetLastError()); 110 | return FALSE; 111 | } 112 | 113 | schService = CreateService( 114 | schSCManager, // SCManager database 115 | service_name, // name of service 116 | display_name, // service name to display 117 | SERVICE_ALL_ACCESS, // desired access 118 | SERVICE_WIN32_OWN_PROCESS, // service type 119 | SERVICE_AUTO_START, // start type 120 | SERVICE_ERROR_NORMAL, // error control type 121 | szPath, // path to service's binary 122 | NULL, // no load ordering group 123 | NULL, // no tag identifier 124 | NULL, // no dependencies 125 | NULL, // LocalSystem account 126 | NULL); // no password 127 | 128 | if (schService == NULL) { 129 | debug("CreateService failed (%d)", (int)GetLastError()); 130 | return FALSE; 131 | } else { 132 | CloseServiceHandle(schService); 133 | return TRUE; 134 | } 135 | } 136 | 137 | int install_service(char *service_name) 138 | { 139 | SC_HANDLE schSCManager; 140 | 141 | // Open a handle to the SC Manager database. 142 | 143 | debug("install_service()", 0); 144 | 145 | schSCManager = OpenSCManager( 146 | NULL, // local machine 147 | NULL, // ServicesActive database 148 | SC_MANAGER_ALL_ACCESS); // full access rights 149 | 150 | if (NULL == schSCManager) 151 | debug("OpenSCManager failed (%d)", (int)GetLastError()); 152 | 153 | if (CreatePenService(schSCManager, service_name, service_name)) { 154 | debug("Success"); 155 | } else { 156 | debug("Failure"); 157 | } 158 | return 0; 159 | } 160 | 161 | int service_main(int argc, char **argv) 162 | { 163 | SERVICE_TABLE_ENTRY DispatchTable[] = { 164 | /* http://msdn.microsoft.com/en-us/library/ms686001(VS.85).aspx 165 | If the service is installed with the SERVICE_WIN32_OWN_PROCESS service type, 166 | this member is ignored, but cannot be NULL. 167 | This member can be an empty string (""). 168 | */ 169 | { /*SERVICE_NAME*/"", ServiceStart }, 170 | { NULL, NULL } 171 | }; 172 | 173 | debug("service_main(%d, %p)", argc, argv); 174 | 175 | if (!StartServiceCtrlDispatcher( DispatchTable)) { 176 | debug(" [PEN] StartServiceCtrlDispatcher (%d)\n", 177 | GetLastError()); 178 | } 179 | return 0; 180 | } 181 | 182 | static void WINAPI ServiceStart (DWORD argc, LPTSTR *argv) 183 | { 184 | DWORD status; 185 | DWORD specificError; 186 | 187 | debug("ServiceStart()"); 188 | ServiceStatus.dwServiceType = SERVICE_WIN32; 189 | ServiceStatus.dwCurrentState = SERVICE_START_PENDING; 190 | ServiceStatus.dwControlsAccepted = 191 | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; 192 | ServiceStatus.dwWin32ExitCode = 0; 193 | ServiceStatus.dwServiceSpecificExitCode = 0; 194 | ServiceStatus.dwCheckPoint = 0; 195 | ServiceStatus.dwWaitHint = 0; 196 | 197 | ServiceStatusHandle = RegisterServiceCtrlHandler( 198 | /* http://msdn.microsoft.com/en-us/library/ms685054(VS.85).aspx 199 | If the service type is SERVICE_WIN32_OWN_PROCESS, the function does not 200 | verify that the specified name is valid, because there is only one 201 | registered service in the process. */ 202 | "", // SERVICE_NAME 203 | ServiceCtrlHandler); 204 | 205 | if (ServiceStatusHandle == (SERVICE_STATUS_HANDLE)0) { 206 | debug(" [PEN] RegisterServiceCtrlHandler failed %d\n", GetLastError()); 207 | return; 208 | } 209 | 210 | // Initialization code goes here. 211 | status = ServiceInitialization(argc,argv, &specificError); 212 | 213 | // Handle error condition 214 | if (status != NO_ERROR) { 215 | ServiceStatus.dwCurrentState = SERVICE_STOPPED; 216 | ServiceStatus.dwCheckPoint = 0; 217 | ServiceStatus.dwWaitHint = 0; 218 | ServiceStatus.dwWin32ExitCode = status; 219 | ServiceStatus.dwServiceSpecificExitCode = specificError; 220 | 221 | SetServiceStatus (ServiceStatusHandle, &ServiceStatus); 222 | return; 223 | } 224 | 225 | // Initialization complete - report running status. 226 | ServiceStatus.dwCurrentState = SERVICE_RUNNING; 227 | ServiceStatus.dwCheckPoint = 0; 228 | ServiceStatus.dwWaitHint = 0; 229 | 230 | if (!SetServiceStatus (ServiceStatusHandle, &ServiceStatus)) { 231 | status = GetLastError(); 232 | debug(" [PEN] SetServiceStatus error %ld\n",status); 233 | } 234 | 235 | // This is where the service does its work. 236 | debug(" [PEN] Returning the Main Thread \n",0); 237 | mainloop(); 238 | 239 | return; 240 | } 241 | 242 | // Stub initialization function. 243 | static DWORD ServiceInitialization(DWORD argc, LPTSTR *argv, DWORD *specificError) 244 | { 245 | return(0); 246 | } 247 | 248 | 249 | static VOID WINAPI ServiceCtrlHandler (DWORD Opcode) 250 | { 251 | DWORD status; 252 | 253 | switch(Opcode) { 254 | case SERVICE_CONTROL_PAUSE: 255 | // Do whatever it takes to pause here. 256 | ServiceStatus.dwCurrentState = SERVICE_PAUSED; 257 | break; 258 | 259 | case SERVICE_CONTROL_CONTINUE: 260 | // Do whatever it takes to continue here. 261 | ServiceStatus.dwCurrentState = SERVICE_RUNNING; 262 | break; 263 | 264 | case SERVICE_CONTROL_STOP: 265 | // Do whatever it takes to stop here. 266 | stop_winsock(); 267 | ServiceStatus.dwWin32ExitCode = 0; 268 | ServiceStatus.dwCurrentState = SERVICE_STOPPED; 269 | ServiceStatus.dwCheckPoint = 0; 270 | ServiceStatus.dwWaitHint = 0; 271 | 272 | if (!SetServiceStatus (ServiceStatusHandle, &ServiceStatus)) { 273 | status = GetLastError(); 274 | debug(" [PEN] SetServiceStatus error %ld\n", status); 275 | } 276 | 277 | debug(" [PEN] Leaving Service \n",0); 278 | return; 279 | 280 | case SERVICE_CONTROL_INTERROGATE: 281 | // Fall through to send current status. 282 | break; 283 | 284 | default: 285 | debug(" [PEN] Unrecognized opcode %ld\n", Opcode); 286 | } 287 | 288 | // Send current status. 289 | if (!SetServiceStatus (ServiceStatusHandle, &ServiceStatus)) { 290 | status = GetLastError(); 291 | debug(" [PEN] SetServiceStatus error %ld\n", status); 292 | } 293 | return; 294 | } 295 | 296 | -------------------------------------------------------------------------------- /windows.h: -------------------------------------------------------------------------------- 1 | #ifdef WINDOWS 2 | #include 3 | #include 4 | #define SHUT_WR SD_SEND /* for shutdown */ 5 | 6 | #define LOG_CONS 0 7 | #define LOG_USER 0 8 | #define LOG_ERR 0 9 | #define LOG_DEBUG 0 10 | 11 | #define CONNECT_IN_PROGRESS (WSAEWOULDBLOCK) 12 | #define WOULD_BLOCK(err) (err == WSAEWOULDBLOCK) 13 | 14 | #define SIGHUP 0 15 | #define SIGUSR1 0 16 | #define SIGPIPE 0 17 | #define SIGCHLD 0 18 | 19 | #ifndef ENOTCONN 20 | #define ENOTCONN WSAENOTCONN 21 | #endif 22 | 23 | typedef int sigset_t; 24 | typedef int siginfo_t; 25 | 26 | struct sigaction { 27 | void (*sa_handler)(int); 28 | void (*sa_sigaction)(int, siginfo_t *, void *); 29 | sigset_t sa_mask; 30 | int sa_flags; 31 | void (*sa_restorer)(void); 32 | }; 33 | 34 | typedef int rlim_t; 35 | 36 | struct rlimit { 37 | rlim_t rlim_cur; 38 | rlim_t rlim_max; 39 | }; 40 | 41 | #define RLIMIT_CORE 0 42 | 43 | typedef int uid_t; 44 | typedef int gid_t; 45 | 46 | struct passwd { 47 | uid_t pw_uid; 48 | gid_t pw_gid; 49 | }; 50 | 51 | extern int delete_service(char *); 52 | extern int install_service(char *); 53 | extern int service_main(int, char **); 54 | 55 | /* misc kludges */ 56 | extern int sigaction(int, const struct sigaction *, struct sigaction *); 57 | extern uid_t getuid(void); 58 | extern int inet_aton(const char *, struct in_addr *addr); 59 | extern void make_nonblocking(int); 60 | extern int start_winsock(void); 61 | 62 | #if 0 63 | /* stuff that exists in Windows that MinGW doesn't know about */ 64 | const char *inet_ntop(int af, const void *src, 65 | char *dst, socklen_t size); 66 | 67 | int inet_pton(int af, const char *src, void *dst); 68 | #endif 69 | #endif /* WINDOWS */ 70 | --------------------------------------------------------------------------------