1package Perlbal::Plugin::XFFExtras;
2
3use strict;
4use warnings;
5
6use Danga::Socket 1.53;  # Need newer Danga::Socket than perlbal to have local_port
7use Perlbal 1.74;        # is_ssl support was added in 1.74
8use Perlbal::BackendHTTP ();
9use Perlbal::Service ();
10
11sub load {
12    Perlbal::Service::add_tunable(
13        send_backend_port => {
14            check_role => 'reverse_proxy',
15            des => "Send an X-Forwarded-Port header to backends to indicate the peer's remote address",
16            check_type => 'bool',
17            default => 0,
18        }
19    );
20    Perlbal::Service::add_tunable(
21        send_backend_proto => {
22            check_role => 'reverse_proxy',
23            des => "Send an X-Forwarded-Proto header to backends to indicate the peers connecting protocol",
24            check_type => 'bool',
25            default => 0,
26        }
27    );
28}
29
30# magical Perlbal hook return value constants
31use constant HANDLE_REQUEST             => 0;
32use constant IGNORE_REQUEST             => 1;
33
34sub register {
35    my ($class, $svc) = @_;
36
37    my $cfg   = $svc->{extra_config}    ||= {};
38
39    $svc->register_hook(XFFExtras => backend_client_assigned => sub {
40        my Perlbal::BackendHTTP $be = shift;
41        my $hds       = $be->{req_headers};
42        my $client    = $be->{client};
43        my $client_ip = $client->peer_ip_string;
44
45        my $trusted = $svc->trusted_ip($client_ip);
46        my $blind   = $svc->{blind_proxy};
47        if (($trusted && !$blind) || !$trusted) {
48            if ($cfg->{send_backend_port}) {
49                # Danga::Socket has no accessor for the peer_port, so we break object
50                # boundaries for now to implement this. Force to integer because D::S
51                # also likes to store string error messages in this field too.
52                $client->local_ip_string;
53                my $local_port = $client->{local_port} + 0;
54                $hds->header("X-Forwarded-Port", $local_port);
55            }
56            if ($cfg->{send_backend_proto}) {
57                my $proto = $client->{is_ssl} ? 'https' : 'http';
58                $hds->header("X-Forwarded-Proto", $proto);
59            }
60        }
61
62        return HANDLE_REQUEST;
63    });
64}
65
661;
67
68__END__
69
70=head1 NAME
71
72Perlbal::Plugin::XFFExtras - Perlbal plugin that can optionally add an
73X-Forwarded-Port and/or X-Forwarded-Proto header to reverse proxied requests.
74
75=head1 SYNOPSIS
76
77    # in perlbal.conf
78
79    LOAD XFFExtra
80
81    CREATE POOL web
82        POOL web ADD 10.0.0.1:80
83
84    CREATE SERVICE proxy
85        SET role                        = reverse_proxy
86        SET listen                      = 0.0.0.0:80
87        SET pool                        = web
88
89        SET plugins             = XFFExtras
90
91        SET send_backend_port   = yes
92        SET send_backend_proto  = yes
93    ENABLE proxy
94
95=head1 DESCRIPTION
96
97This plugin adds optional headers to be sent to backend servers in reverse proxy mode.
98
99=head1 HEADERS
100
101=over 4
102
103=item * B<X-Forwarded-Port>
104
105This header will contain an integer value indicating the port that the peer connected to.
106This will correspond to the port number specified on the listen line of the perlbal service
107that initially handled the connection.
108
109=item * B<X-Forwarded-Proto>
110
111This header will contain a string indicating the protocol the client connected to perlbal
112via. Currently this will be either 'http' or 'https'.
113
114=back
115
116=head1 AUTHOR
117
118Jonathan Steinert, E<lt>hachi@kuiki.netE<gt>
119
120=head1 COPYRIGHT AND LICENSE
121
122Copyright (C) 2012 by Say Media Inc, E<lt>cpan@saymedia.comE<gt>
123
124This library is free software; you can redistribute it and/or modify it under
125the same terms as Perl itself, either Perl version 5.8.6 or, at your option,
126any later version of Perl 5 you may have available.
127
128=cut
129