├── .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 ""
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 |
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 |
267 | EOF
268 | }
269 |
270 | mainpage()
271 | {
272 | header
273 | cat << EOF
274 |
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 |
--------------------------------------------------------------------------------