Attaced patch contains all updated except the config option renaming,
which remains to be sorted out.


On 26/06/2014 7:41 a.m., Alex Rousskov wrote:
> On 06/21/2014 11:15 PM, Amos Jeffries wrote:
>> Support receiving PROXY protocol version 1 and 2.
> 
> 
>> +       proxy-surrogate
>> +                    Support for PROXY protocol version 1 or 2 connections.
>> +                    The proxy_forwarded_access is required to whitelist
>> +                    downstream proxies which can be trusted.
> 
> 
> Could you suggest some alternative names to "proxy-surrogate", which
> sounds like nothing-on-top-of-nothing to me in Squid context?

We are a surrogate for the PROXY protocol. I know its a bit nasty.

> The
> terrible name for the PROXY protocol itself is clearly not your fault,
> but perhaps we can find a more intuitive way to call this new option?
> Here are some suggestions:
> 
>     require-PROXY
>     expect-PROXY
>     require-PROXY-header
>     expect-PROXY-header
> 
> 
> All-CAPS in option names are unfortunate as it goes against Squid style,
> but the poor choice the protocol name essentially forces us to do that.
> 

In all seriousness "haproxy-protocol" is probably the most correct
descriptive right now. But I am trying hard to avoid naming a competitor
in our most visible documentations.

"forwarded" would be wonderful, but I can forsee some confusion with
forward-proxy mode.

How about referred, referral, or proxy-referral ?

> 
>> -NAME: follow_x_forwarded_for
>> +NAME: proxy_forwarded_access follow_x_forwarded_for
> 
> The new name sounds worse than the old one. Hopefully this can be left
> as is or renamed to something better after the "proxy-surrogate" issue
> is resolved.
> 

Is "forwarded_access" obectionable ?
The XFF header has been deprecated by the Forwarded: header now, so a
name-changing was in the cards whatever this patch does.

> 
>> +bool
>> +ConnStateData::proxyProtocolError(bool fatalError)
>> +{
>> +    if (fatalError) {
>> +        // terminate the connection. invalid input.
>> +        stopReceiving("PROXY protocol error");
>> +        stopSending("PROXY protocol error");
>> +    }
>> +    return false;
>> +}
> 
> I recommend using a "const char *" argument type so that you can log a
> meaningful fatalError. Helps with caller code self-documentation too.
> Nil argument would mean non-fatal error, of course.
> 

Done. And calling mustStop() now.

> 
>> +bool
>> +ConnStateData::parseProxyProtocolMagic()
> 
> This appears to parse a lot more than just the magic characters. Please
> rename to parseProxyProtocolHeader() or similar.
> 


The entire PROXY protocol is "magic" connection header. I get the point
though, splitting into three functions. The main one being
findProxyProtocolMagic().

> 
>> +
>> +    needProxyProtocolHeader_ = xact->squidPort->flags.proxySurrogate;
>> +    if (needProxyProtocolHeader_)
>> +        proxyProtocolValidateClient(); // will close the connection on 
>> failure
> 
> Please do not place things that require job protection in a class
> constructor. Calling things like stopSending() and stopReceiving() does
> not (or will not) work well when we are just constructing a
> ConnStateData object. Nobody may notice (at the right time) that you
> "stopped" something because nothing has been started yet.

Did not require job protection until converting
stopSending()/stopReceiving() into mustStop() calls.

Also, note that ConnStateData is not a true AsyncJob. It never started
with AsynJob::Start() and cleanup is equally nasty as this setup
constructor (prior to any changes I am adding).

I have done as suggested and created the AsyncJob::Start() functionality
for it.

However, this means that the PROXY protocol is no longer capable of
being used on https_port. The PROXY protocol header comes before TLS
negotiation on the wire, but ConnStateData::start() is only called by
our code after SSL/TLS negotiation and SSL-bump operations complete.


> 
>> +    // TODO: we should also pass the port details for myportname here.
> 
> Is there a good reason not to pass the port details now? Are they not
> available to ConnStateData?
> 

No reason. Done.

> 
>> +    if (ch.fastCheck() != ACCESS_ALLOWED) {
>> +        // terminate the connection. invalid input.
>> +        stopReceiving("PROXY client not permitted by ACLs");
>> +        stopSending("PROXY client not permitted by ACLs");
>> +    }
> 
> The copied(?) comment is wrong in this context. It is not the input that
> is invalid in this case. However, I think you should call
> proxyProtocolError() here instead of duplicating that code. The char*
> parameter discussed above will make it more suitable for all kinds of
> PROXY errors, not just the input parsing errors.
> 

Done.

> 
>> +            // parse  src-IP SP dst-IP SP src-port SP dst-port CRLF
>> +            if (!tok.prefix(ipa, ipChars) || !tok.skip(' ') ||
>> +               !tok.prefix(ipb, ipChars) || !tok.skip(' ') ||
>> +               !tok.int64(porta) || !tok.skip(' ') ||
>> +               !tok.int64(portb) || !tok.skip('\r') || !tok.skip('\n'))
>> +                return proxyProtocolError(!tok.atEnd());
>> +
>> +            // XXX parse IP and port strings
>> +            Ip::Address originalClient, originalDest;
>> +
>> +            if (!originalClient.GetHostByName(ipa.c_str()))
>> +                return proxyProtocolError(true);
>> +
>> +            if (!originalDest.GetHostByName(ipb.c_str()))
>> +                return false;
>> +
>> +            if (porta > 0 && porta <= 0xFFFF) // max uint16_t
>> +                originalClient.port(static_cast<uint16_t>(porta));
>> +            else
>> +                return proxyProtocolError(true);
>> +
>> +            if (portb > 0 && portb <= 0xFFFF) // max uint16_t
>> +                originalDest.port(static_cast<uint16_t>(portb));
>> +            else
>> +                return proxyProtocolError(true);
>> +
>> +            in.buf = tok.remaining(); // sync buffers
>> +            needProxyProtocolHeader_ = false; // found successfully
> 
> The last two lines appear too late to me. You found the header at the
> beginning of the code quoted above. The rest was parsing/validating
> various header components, which is a different matter. The v2 parsing
> code does not have this problem.
> 

Done.

> Please move most of the parseProxyProtocolMagic() body into methods
> dedicated to each protocol version. Leave just the version detection
> code. That method is too long/deep for no good reason IMO.
> 

I disagree on the function style. But done.

> 
>> +            debugs(33, 5, "PROXY protocol on connection " << 
>> clientConnection);
>> +            clientConnection->local = originalDest;
>> +            clientConnection->remote = originalClient;
>> +            debugs(33, 5, "PROXY upgrade: " << clientConnection);
> 
> We use this kind of address resetting code in many places, right? Please
> encapsulate it (together with the debugging) into a
> Connection::resetAddrs() or a similar method.

Two. PROXY/1.0 and PROXY/2.0 parsers. There are similar patterns
elsewhere, but setting one or other address individually.

> 
>> +        stopReceiving("PROXY protocol error");
>> +        stopSending("PROXY protocol error");
> 
> The two methods are designed for situations where you actually want to
> stop doing _one_ thing, not both. When you want to stop both, it is
> better to call mustStop(). The swanSong() method will close the
> connection for you in that case.
> 
> You may argue that we might reach the new mustStop() call outside job
> protections, but I hope that is not possible because we should always be
> reading new data from an async reading handler.

Okay. Using mustStop().

> 
> 
>> +    /// whether PROXY protocol header is still expected on this port
>> +    bool needProxyProtocolHeader_;
> 
> s/on this port/on this connection/ or s/on this port//.
> 

Fixed.

> 
> * When, in a misconfigured setup, somebody sends a PROXY header to a
> regular Squid HTTP port, does the Squid error look obvious/clear enough?
> Or will the admin have a hard time understanding why things do not work
> in that case?
> 

Trunk will die with a 400 error quoting the PROXY header as the
"URL" or buffer content. After Parser-NG merge Squid will 400 error
(which quotes the input buffer contents IIRC). It seems clear enough to
me not to need new code for that type of config error case.

Amos
=== modified file 'doc/release-notes/release-3.5.sgml'
--- doc/release-notes/release-3.5.sgml  2014-06-22 07:05:08 +0000
+++ doc/release-notes/release-3.5.sgml  2014-07-13 04:24:07 +0000
@@ -26,40 +26,41 @@
 <sect1>Known issues
 <p>
 Although this release is deemed good enough for use in many setups, please 
note the existence of 
 <url 
url="http://bugs.squid-cache.org/buglist.cgi?query_format=advanced&amp;product=Squid&amp;bug_status=UNCONFIRMED&amp;bug_status=NEW&amp;bug_status=ASSIGNED&amp;bug_status=REOPENED&amp;version=3.5";
 name="open bugs against Squid-3.5">.
 
 <sect1>Changes since earlier releases of Squid-3.5
 <p>
 The 3.5 change history can be <url 
url="http://www.squid-cache.org/Versions/v3/3.5/changesets/"; name="viewed 
here">.
 
 
 <sect>Major new features since Squid-3.4
 <p>Squid 3.5 represents a new feature release above 3.4.
 
 <p>The most important of these new features are:
 <itemize>
        <item>Support libecap v1.0
        <item>Authentication helper query extensions
        <item>Support named services
        <item>Upgraded squidclient tool
        <item>Helper support for concurrency channels
+       <item>Support PROXY protocol
 </itemize>
 
 Most user-facing changes are reflected in squid.conf (see below).
 
 
 <sect1>Support libecap v1.0
 <p>Details at <url url="http://wiki.squid-cache.org/Features/BLAH";>.
 
 <p>The new libecap version allows Squid to better check the version of
   the eCAP adapter being loaded as well as the version of the eCAP library
   being used.
 
 <p>Squid-3.5 can support eCAP adapters built with libecap v1.0,
    but no longer supports adapters built with earlier libecap versions
    due to API changes.
 
 
 <sect1>Authentication helper query extensions
 <p>Details at <url url="http://www.squid-cache.org/Doc/config/auth_param/";>.
 
@@ -146,71 +147,103 @@
    The default is to use X.509 certificate encryption instead.
 
 <p>When performing TLS/SSL server certificates are always verified, the
    results shown at debug level 3. The encrypted type is displayed at debug
    level 2 and the connection is used to send and receive the messages
    regardless of verification results.
 
 
 <sect1>Helper support for concurrency channels
 <p>Helper concurrency greatly reduces the communication lag between Squid
    and its helpers allowing faster transaction speeds even on sequential
    helpers.
 
 <p>The Digest authentication, Store-ID, and URL-rewrite helpers packaged
    with Squid have been updated to support concurrency channels. They will
    auto-detect the <em>channel-ID</em> field and will produce the appropriate
    response format.
    With these helpers concurrency may now be set to 0 or any higher number as 
desired.
 
 
+<sect1>Support PROXY protocol
+<p>More info at <url 
url="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt";>
+
+<p>PROXY protocol provides a simple way for proxies and tunnels of any kind to
+   relay the original client source details without having to alter or 
understand
+   the protocol being relayed on the connection.
+
+<p>Squid currently supports receiving HTTP via version 1 or 2 of the protocol.
+   A port which has been configured to receive this protocol may only be used 
to
+   receive traffic from client software sending in this protocol.
+   Regular forward-proxy HTTP traffic is not accepted.
+
+<p>Squid can be configured by adding an <em>http_port</em>
+   with the <em>proxy-surrogate</em> mode flag. The 
<em>proxy_forwarded_access</em>
+   must also be configured with <em>src</em> ACLs to whitelist proxies which 
are
+   trusted to send correct client details.
+
+<p>
+<verbatim>
+ http_port 3128 proxy-surrogate
+ proxy_forwarded_access allow localhost
+</verbatim>
+
+<p><em>Known Issue:</em> Due to design issues HTTPS traffic is not yet accepted
+   over this protocol. So use of <em>proxy-surrogate</em> on 
<em>https_port</em>
+   is not supported.
+
+
 <sect>Changes to squid.conf since Squid-3.4
 <p>
 There have been changes to Squid's configuration file since Squid-3.4.
 
 <p>Squid supports reading configuration option parameters from external
    files using the syntax <em>parameters("/path/filename")</em>. For example:
 <verb>
     acl whitelist dstdomain parameters("/etc/squid/whitelist.txt")
 </verb>
 
 <p>The squid.conf macro ${service_name} is added to provide the service name
    of the process parsing the config.
 
 <p>There have also been changes to individual directives in the config file.
 
 This section gives a thorough account of those changes in three categories:
 
 <itemize>
        <item><ref id="newtags" name="New tags">
        <item><ref id="modifiedtags" name="Changes to existing tags">
        <item><ref id="removedtags" name="Removed tags">
 </itemize>
 <p>
 
 <sect1>New tags<label id="newtags">
 <p>
 <descrip>
        <tag>collapsed_forwarding</tag>
        <p>Ported from Squid-2 with no configuration or visible behaviour 
changes.
            Collapsing of requests is performed across SMP workers.
 
+       <tag>proxy_forwarded_access</tag>
+       <p>Renamed from <em>follow_x_forwarded_for</em> and extended to control 
more
+          ways for locating the indirect (original) client IP details.
+
        <tag>send_hit</tag>
        <p>New configuration directive to enable/disable sending cached content
           based on ACL selection. ACL can be based on client request or cached
           response details.
 
        <tag>sslproxy_session_cache_size</tag>
        <p>New directive which sets the cache size to use for TLS/SSL sessions 
cache.
 
        <tag>sslproxy_session_ttl</tag>
        <p>New directive to specify the time in seconds the TLS/SSL session is 
valid.
 
        <tag>store_id_extras</tag>
        <p>New directive to send additional lookup parameters to the configured
           Store-ID helper program. It takes a string which may contain 
logformat %macros.
        <p>The Store-ID helper input format is now:
        <verb>
          [channel-ID] url [extras]
        </verb>
        <p>The default value for extras is: "%&gt;a/%>A %un %>rm myip=%la 
myport=%lp"
 
@@ -294,40 +327,43 @@
 <sect1>Removed tags<label id="removedtags">
 <p>
 <descrip>
        <tag>cache_dir</tag>
        <p><em>COSS</em> storage type is formally replaced by Rock storage type.
 
        <tag>cache_dns_program</tag>
        <p>DNS external helper interface has been removed. It was no longer
           able to provide high performance service and the internal DNS
           client library with multicast DNS cover all modern use-cases.
 
        <tag>cache_peer</tag>
        <p><em>idle=</em> replaced by <em>standby=</em>.
        <p>NOTE that standby connections are started earlier and available in
           more circumstances than squid-2 idle connections were. They are
           also spread over all IPs of the peer.
 
        <tag>dns_children</tag>
        <p>DNS external helper interface has been removed.
 
+       <tag>follow_x_forwarded_for</tag>
+       <p>Renamed <em>proxy_forwarded_access</em> and extended.
+
 </descrip>
 
 
 <sect>Changes to ./configure options since Squid-3.4
 <p>
 There have been some changes to Squid's build configuration since Squid-3.4.
 
 This section gives an account of those changes in three categories:
 
 <itemize>
        <item><ref id="newoptions" name="New options">
        <item><ref id="modifiedoptions" name="Changes to existing options">
        <item><ref id="removedoptions" name="Removed options">
 </itemize>
 
 
 <sect1>New options<label id="newoptions">
 <p>
 <descrip>
        <p><em>There are no new ./configure options in Squid-3.5.</em>

=== modified file 'doc/rfc/1-index.txt'
--- doc/rfc/1-index.txt 2014-06-09 01:38:06 +0000
+++ doc/rfc/1-index.txt 2014-06-21 12:08:24 +0000
@@ -1,40 +1,45 @@
 draft-ietf-radext-digest-auth-06.txt
        RADIUS Extension for Digest Authentication
        A proposed extension to Radius for Digest authentication
        via RADIUS servers.
 
 draft-cooper-webi-wpad-00.txt
 draft-ietf-svrloc-wpad-template-00.txt
        Web Proxy Auto-Discovery Protocol -- WPAD
        documents how MSIE and several other browsers automatically
        find their proxy settings from DHCP and/or DNS
 
 draft-forster-wrec-wccp-v1-00.txt
        WCCP 1.0
 
 draft-wilson-wccp-v2-12-oct-2001.txt
        WCCP 2.0
 
 draft-vinod-carp-v1-03.txt
        Microsoft CARP peering algorithm
 
+proxy-protocol.txt
+       Documents Proxy Protocol 1.5, for communicating original client IP
+       details between consenting proxies and servers even when
+       transparent interception is taking place.
+
 rfc0959.txt
        FTP
 
 rfc1035.txt
        DNS for IPv4
 
 rfc1157.txt
        A Simple Network Management Protocol (SNMP)
        SNMP v1 Specification. SNMP v2 is documented in several RFCs,
        namely, 1902,1903,1904,1905,1906,1907.
 
 rfc1738.txt
        Uniform Resource Locators (URL)
        (updated by RFC 3986, but not obsoleted)
 
 rfc1902.txt
        Structure of Managament Information (SMI) for SNMPv2
        Management information is viewed as a collection of managed objects,
        the Management Information Base (MIB). MIB modules are
        written using an adapted subset of OSI's Abstract Syntax

=== modified file 'src/Makefile.am'
--- src/Makefile.am     2014-06-24 22:52:53 +0000
+++ src/Makefile.am     2014-07-12 15:09:08 +0000
@@ -1598,40 +1598,41 @@
        acl/libapi.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        comm/libcomm.la \
        eui/libeui.la \
        icmp/libicmp.la icmp/libicmp-core.la \
        log/liblog.la \
        format/libformat.la \
        $(REPL_OBJS) \
        $(DISK_LIBS) \
        $(DISK_OS_LIBS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
        $(SSL_LIBS) \
        anyp/libanyp.la \
        ipc/libipc.la \
        mgr/libmgr.la \
        $(SNMP_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(NETTLELIB) \
        $(REGEXLIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_testCacheManager_LDFLAGS = $(LIBADD_DL)
 tests_testCacheManager_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
 
 tests_testDiskIO_SOURCES = \
        CacheDigest.h \
        tests/stub_CacheDigest.cc \
        cbdata.cc \
@@ -2026,40 +2027,41 @@
        $(DISKIO_GEN_SOURCE)
 tests_testEvent_LDADD = \
        http/libsquid-http.la \
        ident/libident.la \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        anyp/libanyp.la \
        icmp/libicmp.la icmp/libicmp-core.la \
        comm/libcomm.la \
        log/liblog.la \
        format/libformat.la \
        $(REPL_OBJS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
        $(SSL_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(DISK_LIBS) \
        $(DISK_OS_LIBS) \
        ipc/libipc.la \
        mgr/libmgr.la \
        $(SNMP_LIBS) \
        $(NETTLELIB) \
        $(REGEXLIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_testEvent_LDFLAGS = $(LIBADD_DL)
 tests_testEvent_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
@@ -2276,40 +2278,41 @@
        $(DISKIO_GEN_SOURCE)
 tests_testEventLoop_LDADD = \
        http/libsquid-http.la \
        ident/libident.la \
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        anyp/libanyp.la \
        icmp/libicmp.la icmp/libicmp-core.la \
        comm/libcomm.la \
        log/liblog.la \
        format/libformat.la \
        $(REPL_OBJS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
        $(SSL_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(DISK_LIBS) \
        $(DISK_OS_LIBS) \
        ipc/libipc.la \
        mgr/libmgr.la \
        $(SNMP_LIBS) \
        $(NETTLELIB) \
        $(REGEXLIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_testEventLoop_LDFLAGS = $(LIBADD_DL)
 tests_testEventLoop_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
@@ -2524,40 +2527,41 @@
        acl/libstate.la \
        acl/libapi.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        anyp/libanyp.la \
        icmp/libicmp.la icmp/libicmp-core.la \
        comm/libcomm.la \
        log/liblog.la \
        format/libformat.la \
        $(REPL_OBJS) \
        $(DISK_LIBS) \
        $(DISK_OS_LIBS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
        $(SSL_LIBS) \
        ipc/libipc.la \
        base/libbase.la \
        mgr/libmgr.la \
        $(SNMP_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(NETTLELIB) \
        $(REGEXLIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_test_http_range_LDFLAGS = $(LIBADD_DL)
 tests_test_http_range_DEPENDENCIES = \
        $(SQUID_CPPUNIT_LA)
 
 tests_testHttpParser_SOURCES = \
        Debug.h \
        HttpParser.cc \
        HttpParser.h \
        MemBuf.cc \
@@ -2814,40 +2818,41 @@
        acl/libacls.la \
        acl/libstate.la \
        acl/libapi.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        $(SSL_LIBS) \
        ipc/libipc.la \
        base/libbase.la \
        mgr/libmgr.la \
        anyp/libanyp.la \
        $(SNMP_LIBS) \
        icmp/libicmp.la icmp/libicmp-core.la \
        comm/libcomm.la \
        log/liblog.la \
        format/libformat.la \
        http/libsquid-http.la \
        $(REPL_OBJS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(DISK_OS_LIBS) \
        $(NETTLELIB) \
        $(REGEXLIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_testHttpRequest_LDFLAGS = $(LIBADD_DL)
 tests_testHttpRequest_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
 
 ## why so many sources? well httpHeaderTools requites ACLChecklist & friends.
 ## first line - what we are testing.
 tests_testStore_SOURCES= \
@@ -3658,40 +3663,41 @@
        eui/libeui.la \
        acl/libstate.la \
        acl/libapi.la \
        base/libbase.la \
        libsquid.la \
        ip/libip.la \
        fs/libfs.la \
        $(SSL_LIBS) \
        ipc/libipc.la \
        mgr/libmgr.la \
        $(SNMP_LIBS) \
        icmp/libicmp.la icmp/libicmp-core.la \
        comm/libcomm.la \
        log/liblog.la \
        $(DISK_OS_LIBS) \
        format/libformat.la \
        $(REGEXLIB) \
        $(REPL_OBJS) \
        $(ADAPTATION_LIBS) \
        $(ESI_LIBS) \
+       parser/libsquid-parser.la \
        $(top_builddir)/lib/libmisccontainers.la \
        $(top_builddir)/lib/libmiscencoding.la \
        $(top_builddir)/lib/libmiscutil.la \
        $(NETTLELIB) \
        $(COMPAT_LIB) \
        $(SQUID_CPPUNIT_LIBS) \
        $(SQUID_CPPUNIT_LA) \
        $(SSLLIB) \
        $(KRB5LIBS) \
        $(COMPAT_LIB) \
        $(XTRA_LIBS)
 tests_testURL_LDFLAGS = $(LIBADD_DL)
 tests_testURL_DEPENDENCIES = \
        $(REPL_OBJS) \
        $(SQUID_CPPUNIT_LA)
 
 tests_testSBuf_SOURCES= \
        tests/testSBuf.h \
        tests/testSBuf.cc \
        tests/testMain.cc \

=== modified file 'src/anyp/TrafficMode.h'
--- src/anyp/TrafficMode.h      2013-02-04 09:47:50 +0000
+++ src/anyp/TrafficMode.h      2014-06-21 13:06:04 +0000
@@ -8,40 +8,50 @@
  * Set of 'mode' flags defining types of trafic which can be received.
  *
  * Use to determine the processing steps which need to be applied
  * to this traffic under any special circumstances which may apply.
  */
 class TrafficMode
 {
 public:
     TrafficMode() : accelSurrogate(false), natIntercept(false), 
tproxyIntercept(false), tunnelSslBumping(false) {}
     TrafficMode(const TrafficMode &rhs) { operator =(rhs); }
     TrafficMode &operator =(const TrafficMode &rhs) { memcpy(this, &rhs, 
sizeof(TrafficMode)); return *this; }
 
     /** marks HTTP accelerator (reverse/surrogate proxy) traffic
      *
      * Indicating the following are required:
      *  - URL translation from relative to absolute form
      *  - restriction to origin peer relay recommended
      */
     bool accelSurrogate;
 
+    /** marks ports receiving PROXY protocol traffic
+     *
+     * Indicating the following are required:
+     *  - PROXY protocol magic header
+     *  - src/dst IP retrieved from magic PROXY header
+     *  - reverse-proxy traffic prohibited
+     *  - intercepted traffic prohibited
+     */
+    bool proxySurrogate;
+
     /** marks NAT intercepted traffic
      *
      * Indicating the following are required:
      *  - NAT lookups
      *  - URL translation from relative to absolute form
      *  - Same-Origin verification is mandatory
      *  - destination pinning is recommended
      *  - authentication prohibited
      */
     bool natIntercept;
 
     /** marks TPROXY intercepted traffic
      *
      * Indicating the following are required:
      *  - src/dst IP inversion must be performed
      *  - client IP should be spoofed if possible
      *  - URL translation from relative to absolute form
      *  - Same-Origin verification is mandatory
      *  - destination pinning is recommended
      *  - authentication prohibited

=== modified file 'src/cache_cf.cc'
--- src/cache_cf.cc     2014-06-24 22:52:53 +0000
+++ src/cache_cf.cc     2014-07-13 02:32:21 +0000
@@ -3549,71 +3549,78 @@
         debugs(3, 3, portType << "_port: Listen on Host/IP: " << host << " --> 
" << s->s);
     } else if ( s->s.GetHostByName(host) ) { /* check/parse for FQDN */
         /* dont use ipcache */
         s->defaultsite = xstrdup(host);
         s->s.port(port);
         if (!Ip::EnableIpv6)
             s->s.setIPv4();
         debugs(3, 3, portType << "_port: found Listen as Host " << 
s->defaultsite << " on IP: " << s->s);
     } else {
         debugs(3, DBG_CRITICAL, "FATAL: " << portType << "_port: failed to 
resolve Host/IP: " << host);
         self_destruct();
     }
 }
 
 static void
 parse_port_option(AnyP::PortCfg * s, char *token)
 {
     /* modes first */
 
     if (strcmp(token, "accel") == 0) {
-        if (s->flags.isIntercepted()) {
+        if (s->flags.isIntercepted() || s->flags.proxySurrogate) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: Accelerator mode 
requires its own port. It cannot be shared with other modes.");
             self_destruct();
         }
         s->flags.accelSurrogate = true;
         s->vhost = true;
     } else if (strcmp(token, "transparent") == 0 || strcmp(token, "intercept") 
== 0) {
-        if (s->flags.accelSurrogate || s->flags.tproxyIntercept) {
+        if (s->flags.accelSurrogate || s->flags.tproxyIntercept || 
s->flags.proxySurrogate) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: Intercept mode 
requires its own interception port. It cannot be shared with other modes.");
             self_destruct();
         }
         s->flags.natIntercept = true;
         Ip::Interceptor.StartInterception();
         /* Log information regarding the port modes under interception. */
         debugs(3, DBG_IMPORTANT, "Starting Authentication on port " << s->s);
         debugs(3, DBG_IMPORTANT, "Disabling Authentication on port " << s->s 
<< " (interception enabled)");
     } else if (strcmp(token, "tproxy") == 0) {
-        if (s->flags.natIntercept || s->flags.accelSurrogate) {
+        if (s->flags.natIntercept || s->flags.accelSurrogate || 
s->flags.proxySurrogate) {
             debugs(3,DBG_CRITICAL, "FATAL: http(s)_port: TPROXY option 
requires its own interception port. It cannot be shared with other modes.");
             self_destruct();
         }
         s->flags.tproxyIntercept = true;
         Ip::Interceptor.StartTransparency();
         /* Log information regarding the port modes under transparency. */
         debugs(3, DBG_IMPORTANT, "Disabling Authentication on port " << s->s 
<< " (TPROXY enabled)");
 
         if (!Ip::Interceptor.ProbeForTproxy(s->s)) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: TPROXY support in 
the system does not work.");
             self_destruct();
         }
 
+    } else if (strcmp(token, "proxy-surrogate") == 0) {
+        if (s->flags.natIntercept || s->flags.accelSurrogate  || 
s->flags.tproxyIntercept) {
+            debugs(3,DBG_CRITICAL, "FATAL: http(s)_port: proxy-surrogate 
option requires its own port. It cannot be shared with other modes.");
+            self_destruct();
+        }
+        s->flags.proxySurrogate = true;
+
     } else if (strncmp(token, "defaultsite=", 12) == 0) {
         if (!s->flags.accelSurrogate) {
             debugs(3, DBG_CRITICAL, "FATAL: http(s)_port: defaultsite option 
requires Acceleration mode flag.");
             self_destruct();
         }
         safe_free(s->defaultsite);
         s->defaultsite = xstrdup(token + 12);
     } else if (strcmp(token, "vhost") == 0) {
         if (!s->flags.accelSurrogate) {
             debugs(3, DBG_CRITICAL, "WARNING: http(s)_port: vhost option is 
deprecated. Use 'accel' mode flag instead.");
         }
         s->flags.accelSurrogate = true;
         s->vhost = true;
     } else if (strcmp(token, "no-vhost") == 0) {
         if (!s->flags.accelSurrogate) {
             debugs(3, DBG_IMPORTANT, "ERROR: http(s)_port: no-vhost option 
requires Acceleration mode flag.");
         }
         s->vhost = false;
     } else if (strcmp(token, "vport") == 0) {
         if (!s->flags.accelSurrogate) {
@@ -3822,40 +3829,43 @@
 
     *head = cbdataReference(s);
 }
 
 static void
 dump_generic_port(StoreEntry * e, const char *n, const AnyP::PortCfg * s)
 {
     char buf[MAX_IPSTRLEN];
 
     storeAppendPrintf(e, "%s %s",
                       n,
                       s->s.toUrl(buf,MAX_IPSTRLEN));
 
     // MODES and specific sub-options.
     if (s->flags.natIntercept)
         storeAppendPrintf(e, " intercept");
 
     else if (s->flags.tproxyIntercept)
         storeAppendPrintf(e, " tproxy");
 
+    else if (s->flags.proxySurrogate)
+        storeAppendPrintf(e, " proxy-surrogate");
+
     else if (s->flags.accelSurrogate) {
         storeAppendPrintf(e, " accel");
 
         if (s->vhost)
             storeAppendPrintf(e, " vhost");
 
         if (s->vport < 0)
             storeAppendPrintf(e, " vport");
         else if (s->vport > 0)
             storeAppendPrintf(e, " vport=%d", s->vport);
 
         if (s->defaultsite)
             storeAppendPrintf(e, " defaultsite=%s", s->defaultsite);
 
         // TODO: compare against prefix of 'n' instead of assuming http_port
         if (s->transport.protocol != AnyP::PROTO_HTTP)
             storeAppendPrintf(e, " protocol=%s", 
AnyP::UriScheme(s->transport.protocol).c_str());
 
         if (s->allow_direct)
             storeAppendPrintf(e, " allow-direct");

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre     2014-07-12 09:34:43 +0000
+++ src/cf.data.pre     2014-07-13 03:57:28 +0000
@@ -1077,49 +1077,57 @@
 acl localnet src 172.16.0.0/12 # RFC1918 possible internal network
 acl localnet src 192.168.0.0/16        # RFC1918 possible internal network
 acl localnet src fc00::/7       # RFC 4193 local private network range
 acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) 
machines
 
 acl SSL_ports port 443
 acl Safe_ports port 80         # http
 acl Safe_ports port 21         # ftp
 acl Safe_ports port 443                # https
 acl Safe_ports port 70         # gopher
 acl Safe_ports port 210                # wais
 acl Safe_ports port 1025-65535 # unregistered ports
 acl Safe_ports port 280                # http-mgmt
 acl Safe_ports port 488                # gss-http
 acl Safe_ports port 591                # filemaker
 acl Safe_ports port 777                # multiling http
 acl CONNECT method CONNECT
 NOCOMMENT_END
 DOC_END
 
-NAME: follow_x_forwarded_for
+NAME: proxy_forwarded_access follow_x_forwarded_for
 TYPE: acl_access
-IFDEF: FOLLOW_X_FORWARDED_FOR
 LOC: Config.accessList.followXFF
 DEFAULT_IF_NONE: deny all
-DEFAULT_DOC: X-Forwarded-For header will be ignored.
+DEFAULT_DOC: indirect client IP will not be accepted.
 DOC_START
-       Allowing or Denying the X-Forwarded-For header to be followed to
-       find the original source of a request.
+       Determine which client proxies can be trusted to provide correct
+       information regarding real client IP address.
+
+       The original source details can be relayed in:
+               HTTP message Forwarded header, or
+               HTTP message X-Forwarded-For header, or
+               PROXY protocol connection header.
+
+       Allowing or Denying the X-Forwarded-For or Forwarded headers to
+       be followed to find the original source of a request. Or permitting
+       a client proxy to connect using PROXY protocol.
 
        Requests may pass through a chain of several other proxies
        before reaching us.  The X-Forwarded-For header will contain a
        comma-separated list of the IP addresses in the chain, with the
        rightmost address being the most recent.
 
        If a request reaches us from a source that is allowed by this
        configuration item, then we consult the X-Forwarded-For header
        to see where that host received the request from.  If the
        X-Forwarded-For header contains multiple addresses, we continue
        backtracking until we reach an address for which we are not allowed
        to follow the X-Forwarded-For header, or until we reach the first
        address in the list. For the purpose of ACL used in the
        follow_x_forwarded_for directive the src ACL type always matches
        the address we are testing and srcdomain matches its rDNS.
 
        The end result of this process is an IP address that we will
        refer to as the indirect client address.  This address may
        be treated as the client address for access control, ICAP, delay
        pools and logging, depending on the acl_uses_indirect_client,
@@ -1518,40 +1526,45 @@
        probably want to listen on port 80 also, or instead.
 
        The -a command line option may be used to specify additional
        port(s) where Squid listens for proxy request. Such ports will
        be plain proxy ports with no options.
 
        You may specify multiple socket addresses on multiple lines.
 
        Modes:
 
           intercept    Support for IP-Layer interception of
                        outgoing requests without browser settings.
                        NP: disables authentication and IPv6 on the port.
 
           tproxy       Support Linux TPROXY for spoofing outgoing
                        connections using the client IP address.
                        NP: disables authentication and maybe IPv6 on the port.
 
           accel        Accelerator / reverse proxy mode
 
+          proxy-surrogate
+                       Support for PROXY protocol version 1 or 2 connections.
+                       The proxy_forwarded_access is required to whitelist
+                       downstream proxies which can be trusted.
+
           ssl-bump     For each CONNECT request allowed by ssl_bump ACLs,
                        establish secure connection with the client and with
                        the server, decrypt HTTPS messages as they pass through
                        Squid, and treat them as unencrypted HTTP messages,
                        becoming the man-in-the-middle.
 
                        The ssl_bump option is required to fully enable
                        bumping of CONNECT requests.
 
        Omitting the mode flag causes default forward proxy mode to be used.
 
 
        Accelerator Mode Options:
 
           defaultsite=domainname
                        What to use for the Host: header if it is not present
                        in a request. Determines what site (not origin server)
                        accelerators should consider the default.
 
           no-vhost     Disable using HTTP/1.1 Host header for virtual domain 
support.

=== modified file 'src/client_side.cc'
--- src/client_side.cc  2014-06-24 22:52:53 +0000
+++ src/client_side.cc  2014-07-13 04:40:28 +0000
@@ -102,40 +102,41 @@
 #include "fd.h"
 #include "fde.h"
 #include "fqdncache.h"
 #include "FwdState.h"
 #include "globals.h"
 #include "http.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ident/Config.h"
 #include "ident/Ident.h"
 #include "internal.h"
 #include "ipc/FdNotes.h"
 #include "ipc/StartListening.h"
 #include "log/access_log.h"
 #include "Mem.h"
 #include "MemBuf.h"
 #include "MemObject.h"
 #include "mime_header.h"
+#include "parser/Tokenizer.h"
 #include "profiler/Profiler.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
 #include "SquidTime.h"
 #include "StatCounters.h"
 #include "StatHist.h"
 #include "Store.h"
 #include "TimeOrTag.h"
 #include "tools.h"
 #include "URL.h"
 
 #if USE_AUTH
 #include "auth/UserRequest.h"
 #endif
 #if USE_DELAY_POOLS
 #include "ClientInfo.h"
 #endif
 #if USE_OPENSSL
 #include "ssl/context_storage.h"
 #include "ssl/gadgets.h"
@@ -2887,67 +2888,304 @@
 bool
 ConnStateData::concurrentRequestQueueFilled() const
 {
     const int existingRequestCount = getConcurrentRequestCount();
 
     // default to the configured pipeline size.
     // add 1 because the head of pipeline is counted in concurrent requests 
and not prefetch queue
     const int concurrentRequestLimit = Config.pipeline_max_prefetch + 1;
 
     // when queue filled already we cant add more.
     if (existingRequestCount >= concurrentRequestLimit) {
         debugs(33, 3, clientConnection << " max concurrent requests reached (" 
<< concurrentRequestLimit << ")");
         debugs(33, 5, clientConnection << " deferring new request until one is 
done");
         return true;
     }
 
     return false;
 }
 
 /**
+ * Perform forwarded_access ACL tests on the client which
+ * connected to PROXY protocol port to see if we trust the
+ * sender enough to accept their PROXY header claim.
+ */
+bool
+ConnStateData::proxyProtocolValidateClient()
+{
+    ACLFilledChecklist ch(Config.accessList.followXFF, NULL, 
clientConnection->rfc931);
+    ch.src_addr = clientConnection->remote;
+    ch.my_addr = clientConnection->local;
+    ch.conn(this);
+
+    if (ch.fastCheck() != ACCESS_ALLOWED)
+        return proxyProtocolError("PROXY client not permitted by ACLs");
+
+    return true;
+}
+
+/**
+ * Perform cleanup on PROXY protocol errors.
+ * If header parsing hits a fatal error terminate the connection,
+ * otherwise wait for more data.
+ */
+bool
+ConnStateData::proxyProtocolError(const char *msg)
+{
+    if (msg)
+        mustStop(msg);
+    return false;
+}
+
+/// magic octet prefix for PROXY protocol version 1
+static const SBuf Proxy10magic("PROXY ", 6);
+
+/// magic octet prefix for PROXY protocol version 2
+static const SBuf 
Proxy20magic("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12);
+
+/**
+ * Test the connection read buffer for PROXY protocol header.
+ * Version 1 and 2 header currently supported.
+ */
+bool
+ConnStateData::findProxyProtocolMagic()
+{
+    // http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt
+
+    // detect and parse PROXY protocol version 1 header
+    if (in.buf.length() > Proxy10magic.length() && 
in.buf.startsWith(Proxy10magic)) {
+         return parseProxy10();
+
+        // detect and parse PROXY protocol version 2 header
+    } else if (in.buf.length() > Proxy20magic.length() && 
in.buf.startsWith(Proxy20magic)) {
+        return parseProxy20();
+
+        // detect and terminate other protocols
+    } else if (in.buf.length() >= Proxy20magic.length()) {
+        // input other than the PROXY header is a protocol error
+        return proxyProtocolError("PROXY protocol error: invalid header");
+    }
+
+    // not enough bytes to parse yet.
+    return false;
+}
+
+/// parse the PROXY/1.0 protocol header from the connection read buffer
+bool
+ConnStateData::parseProxy10()
+{
+    ::Parser::Tokenizer tok(in.buf);
+    tok.skip(Proxy10magic);
+
+    SBuf tcpVersion;
+    if (!tok.prefix(tcpVersion, CharacterSet::ALPHA+CharacterSet::DIGIT))
+        return proxyProtocolError(tok.atEnd()?"PROXY/1.0 error: invalid 
protocol family":NULL);
+
+    if (!tcpVersion.cmp("UNKNOWN")) {
+        // skip to first LF (assumes it is part of CRLF)
+        const SBuf::size_type pos = in.buf.findFirstOf(CharacterSet::LF);
+        if (pos != SBuf::npos) {
+            if (in.buf[pos-1] != '\r')
+                return proxyProtocolError("PROXY/1.0 error: missing CR");
+            // found valid but unusable header
+            in.buf.consume(pos);
+            needProxyProtocolHeader_ = false;
+            return true;
+        }
+        // else, no LF found
+
+        // protocol error only if there are more than 107 bytes prefix header
+        return proxyProtocolError(in.buf.length() > 107? "PROXY error: missing 
CRLF":NULL);
+
+    } else if (!tcpVersion.cmp("TCP",3)) {
+
+        // skip SP after protocol version
+        if (!tok.skip(' '))
+            return proxyProtocolError(tok.atEnd()?"PROXY/1.0 error: missing 
SP":NULL);
+
+        SBuf ipa, ipb;
+        int64_t porta, portb;
+        const CharacterSet ipChars =  CharacterSet("IP Address",".:") + 
CharacterSet::HEXDIG;
+
+        // parse  src-IP SP dst-IP SP src-port SP dst-port CRLF
+        if (!tok.prefix(ipa, ipChars) || !tok.skip(' ') ||
+           !tok.prefix(ipb, ipChars) || !tok.skip(' ') ||
+           !tok.int64(porta) || !tok.skip(' ') ||
+           !tok.int64(portb) || !tok.skip('\r') || !tok.skip('\n'))
+            return proxyProtocolError(!tok.atEnd()?"PROXY/1.0 error: invalid 
syntax":NULL);
+
+        in.buf = tok.remaining(); // sync buffers
+        needProxyProtocolHeader_ = false; // found successfully
+
+        // parse IP and port strings
+        Ip::Address originalClient, originalDest;
+
+        if (!originalClient.GetHostByName(ipa.c_str()))
+            return proxyProtocolError("PROXY/1.0 error: invalid src-IP 
address");
+
+        if (!originalDest.GetHostByName(ipb.c_str()))
+            return proxyProtocolError("PROXY/1.0 error: invalid dst-IP 
address");
+
+        if (porta > 0 && porta <= 0xFFFF) // max uint16_t
+            originalClient.port(static_cast<uint16_t>(porta));
+        else
+            return proxyProtocolError("PROXY/1.0 error: invalid src port");
+
+        if (portb > 0 && portb <= 0xFFFF) // max uint16_t
+            originalDest.port(static_cast<uint16_t>(portb));
+        else
+            return proxyProtocolError("PROXY/1.0 error: invalid dst port");
+
+        // we have original client and destination details now
+        // replace the client connection values
+        debugs(33, 5, "PROXY/1.0 protocol on connection " << clientConnection);
+        clientConnection->local = originalDest;
+        clientConnection->remote = originalClient;
+        debugs(33, 5, "PROXY/1.0 upgrade: " << clientConnection);
+
+        // repeat fetch ensuring the new client FQDN can be logged
+        if (Config.onoff.log_fqdn)
+            fqdncache_gethostbyaddr(clientConnection->remote, 
FQDN_LOOKUP_IF_MISS);
+
+        return true;
+    }
+
+    return false;
+}
+
+/// parse the PROXY/2.0 protocol header from the connection read buffer
+bool
+ConnStateData::parseProxy20()
+{
+    if ((in.buf[0] & 0xF0) != 0x20) // version == 2 is mandatory
+        return proxyProtocolError("PROXY/2.0 error: invalid version");
+
+    const char command = (in.buf[0] & 0x0F);
+    if ((command & 0xFE) != 0x00) // values other than 0x0-0x1 are invalid
+        return proxyProtocolError("PROXY/2.0 error: invalid command");
+
+    const char family = (in.buf[1] & 0xF0) >>4;
+    if (family > 0x3) // values other than 0x0-0x3 are invalid
+        return proxyProtocolError("PROXY/2.0 error: invalid family");
+
+    const char proto = (in.buf[1] & 0x0F);
+    if (proto > 0x2) // values other than 0x0-0x2 are invalid
+        return proxyProtocolError("PROXY/2.0 error: invalid protocol type");
+
+    const char *clen = in.buf.rawContent() + Proxy20magic.length() + 2;
+    const uint16_t len = ntohs(*(reinterpret_cast<const uint16_t *>(clen)));
+
+    if (in.buf.length() < Proxy20magic.length() + 4 + len)
+        return false; // need more bytes
+
+    in.buf.consume(Proxy20magic.length() + 4); // 4 being the extra bytes
+    const SBuf extra = in.buf.consume(len);
+    needProxyProtocolHeader_ = false; // found successfully
+
+    // LOCAL connections do nothing with the extras
+    if (command == 0x00/* LOCAL*/)
+        return true;
+
+    typedef union proxy_addr {
+        struct {        /* for TCP/UDP over IPv4, len = 12 */
+            struct in_addr src_addr;
+            struct in_addr dst_addr;
+            uint16_t src_port;
+            uint16_t dst_port;
+        } ipv4_addr;
+        struct {        /* for TCP/UDP over IPv6, len = 36 */
+             struct in6_addr src_addr;
+             struct in6_addr dst_addr;
+             uint16_t src_port;
+             uint16_t dst_port;
+        } ipv6_addr;
+#if NOT_SUPPORTED
+        struct {        /* for AF_UNIX sockets, len = 216 */
+             uint8_t src_addr[108];
+             uint8_t dst_addr[108];
+        } unix_addr;
+#endif
+    } pax;
+
+    const pax *ipu = reinterpret_cast<const pax*>(extra.rawContent());
+
+    // replace the client connection values
+    debugs(33, 5, "PROXY/2.0 protocol on connection " << clientConnection);
+    switch (family)
+    {
+    case 0x1: // IPv4
+        clientConnection->local = ipu->ipv4_addr.dst_addr;
+        clientConnection->local.port(ntohs(ipu->ipv4_addr.dst_port));
+        clientConnection->remote = ipu->ipv4_addr.src_addr;
+        clientConnection->remote.port(ntohs(ipu->ipv4_addr.src_port));
+        break;
+    case 0x2: // IPv6
+        clientConnection->local = ipu->ipv6_addr.dst_addr;
+        clientConnection->local.port(ntohs(ipu->ipv6_addr.dst_port));
+        clientConnection->remote = ipu->ipv6_addr.src_addr;
+        clientConnection->remote.port(ntohs(ipu->ipv6_addr.src_port));
+        break;
+    default: // do nothing
+        break;
+    }
+    debugs(33, 5, "PROXY/2.0 upgrade: " << clientConnection);
+
+    // repeat fetch ensuring the new client FQDN can be logged
+    if (Config.onoff.log_fqdn)
+        fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
+
+    return true;
+}
+
+/**
  * Attempt to parse one or more requests from the input buffer.
  * If a request is successfully parsed, even if the next request
  * is only partially parsed, it will return TRUE.
  */
 bool
 ConnStateData::clientParseRequests()
 {
     HttpRequestMethod method;
     bool parsed_req = false;
 
     debugs(33, 5, HERE << clientConnection << ": attempting to parse");
 
     // Loop while we have read bytes that are not needed for producing the body
     // On errors, bodyPipe may become nil, but readMore will be cleared
     while (!in.buf.isEmpty() && !bodyPipe && flags.readMore) {
         connStripBufferWhitespace(this);
 
         /* Don't try to parse if the buffer is empty */
         if (in.buf.isEmpty())
             break;
 
         /* Limit the number of concurrent requests */
         if (concurrentRequestQueueFilled())
             break;
 
         /* Begin the parsing */
         PROF_start(parseHttpRequest);
+
+        // try to parse the PROXY protocol header magic bytes
+        if (needProxyProtocolHeader_ && !findProxyProtocolMagic())
+            break;
+
         HttpParserInit(&parser_, in.buf.c_str(), in.buf.length());
 
         /* Process request */
         Http::ProtocolVersion http_ver;
         ClientSocketContext *context = parseHttpRequest(this, &parser_, 
&method, &http_ver);
         PROF_stop(parseHttpRequest);
 
         /* partial or incomplete request */
         if (!context) {
             // TODO: why parseHttpRequest can just return parseHttpRequestAbort
             // (which becomes context) but checkHeaderLimits cannot?
             checkHeaderLimits();
             break;
         }
 
         /* status -1 or 1 */
         if (context) {
             debugs(33, 5, HERE << clientConnection << ": parsed a request");
             AsyncCall::Pointer timeoutCall = commCbCall(5, 4, 
"clientLifetimeTimeout",
                                              
CommTimeoutCbPtrFun(clientLifetimeTimeout, context->http));
@@ -3265,118 +3503,135 @@
         sslBumpMode(Ssl::bumpEnd),
         switchedToHttps_(false),
         sslServerBump(NULL),
 #endif
         stoppedSending_(NULL),
         stoppedReceiving_(NULL)
 {
     pinning.host = NULL;
     pinning.port = -1;
     pinning.pinned = false;
     pinning.auth = false;
     pinning.zeroReply = false;
     pinning.peer = NULL;
 
     // store the details required for creating more MasterXaction objects as 
new requests come in
     clientConnection = xact->tcpClient;
     port = cbdataReference(xact->squidPort.get());
     log_addr = xact->tcpClient->remote;
     log_addr.applyMask(Config.Addrs.client_netmask);
 
-    // ensure a buffer is present for this connection
-    in.maybeMakeSpaceAvailable();
-
+    // XXX: should do this in start(), but SSL/TLS operations begin before 
start() is called
     if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF &&
             (transparent() || port->disable_pmtu_discovery == 
DISABLE_PMTU_ALWAYS)) {
 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
         int i = IP_PMTUDISC_DONT;
         if (setsockopt(clientConnection->fd, SOL_IP, IP_MTU_DISCOVER, &i, 
sizeof(i)) < 0)
             debugs(33, 2, "WARNING: Path MTU discovery disabling failed on " 
<< clientConnection << " : " << xstrerror());
 #else
         static bool reported = false;
 
         if (!reported) {
             debugs(33, DBG_IMPORTANT, "NOTICE: Path MTU discovery disabling is 
not supported on your platform.");
             reported = true;
         }
 #endif
     }
+}
+
+void
+ConnStateData::start()
+{
+    // ensure a buffer is present for this connection
+    in.maybeMakeSpaceAvailable();
 
     typedef CommCbMemFunT<ConnStateData, CommCloseCbParams> Dialer;
     AsyncCall::Pointer call = JobCallback(33, 5, Dialer, this, 
ConnStateData::connStateClosed);
     comm_add_close_handler(clientConnection->fd, call);
 
     if (Config.onoff.log_fqdn)
         fqdncache_gethostbyaddr(clientConnection->remote, FQDN_LOOKUP_IF_MISS);
 
 #if USE_IDENT
     if (Ident::TheConfig.identLookup) {
         ACLFilledChecklist identChecklist(Ident::TheConfig.identLookup, NULL, 
NULL);
-        identChecklist.src_addr = xact->tcpClient->remote;
-        identChecklist.my_addr = xact->tcpClient->local;
+        identChecklist.src_addr = clientConnection->remote;
+        identChecklist.my_addr = clientConnection->local;
         if (identChecklist.fastCheck() == ACCESS_ALLOWED)
-            Ident::Start(xact->tcpClient, clientIdentDone, this);
+            Ident::Start(clientConnection, clientIdentDone, this);
     }
 #endif
 
     clientdbEstablished(clientConnection->remote, 1);
 
+    needProxyProtocolHeader_ = port->flags.proxySurrogate;
+    if (needProxyProtocolHeader_) {
+        if (!proxyProtocolValidateClient()) // will close the connection on 
failure
+            return;
+    }
+
+    // prepare any child API state that is needed
+    BodyProducer::start();
+    HttpControlMsgSink::start();
+
+    // if all is well, start reading
     flags.readMore = true;
+    readSomeData();
 }
 
 /** Handle a new connection on HTTP socket. */
 void
 httpAccept(const CommAcceptCbParams &params)
 {
     MasterXaction::Pointer xact = params.xaction;
     AnyP::PortCfgPointer s = xact->squidPort;
 
     if (!s.valid()) {
         // it is possible the call or accept() was still queued when the port 
was reconfigured
         debugs(33, 2, "HTTP accept failure: port reconfigured.");
         return;
     }
 
     if (params.flag != Comm::OK) {
         // Its possible the call was still queued when the client disconnected
         debugs(33, 2, "httpAccept: " << s->listenConn << ": accept failure: " 
<< xstrerr(params.xerrno));
         return;
     }
 
     debugs(33, 4, HERE << params.conn << ": accepted");
     fd_note(params.conn->fd, "client http connect");
 
     if (s->tcp_keepalive.enabled) {
         commSetTcpKeepalive(params.conn->fd, s->tcp_keepalive.idle, 
s->tcp_keepalive.interval, s->tcp_keepalive.timeout);
     }
 
     ++ incoming_sockets_accepted;
 
     // Socket is ready, setup the connection manager to start using it
     ConnStateData *connState = new ConnStateData(xact);
 
     typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall =  JobCallback(33, 5,
                                       TimeoutDialer, connState, 
ConnStateData::requestTimeout);
     commSetConnTimeout(params.conn, Config.Timeout.request, timeoutCall);
 
-    connState->readSomeData();
+    AsyncJob::Start(connState);
 
 #if USE_DELAY_POOLS
     fd_table[params.conn->fd].clientInfo = NULL;
 
     if (Config.onoff.client_db) {
         /* it was said several times that client write limiter does not work 
if client_db is disabled */
 
         ClientDelayPools& pools(Config.ClientDelay.pools);
         ACLFilledChecklist ch(NULL, NULL, NULL);
 
         // TODO: we check early to limit error response bandwith but we
         // should recheck when we can honor delay_pool_uses_indirect
         // TODO: we should also pass the port details for myportname here.
         ch.src_addr = params.conn->remote;
         ch.my_addr = params.conn->local;
 
         for (unsigned int pool = 0; pool < pools.size(); ++pool) {
 
             /* pools require explicit 'allow' to assign a client into them */
             if (pools[pool].access) {
@@ -3530,41 +3785,41 @@
     debugs(83, 3, "clientNegotiateSSL: FD " << fd << " negotiated cipher " <<
            SSL_get_cipher(ssl));
 
     client_cert = SSL_get_peer_certificate(ssl);
 
     if (client_cert != NULL) {
         debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
                " client certificate: subject: " <<
                X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0));
 
         debugs(83, 3, "clientNegotiateSSL: FD " << fd <<
                " client certificate: issuer: " <<
                X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0));
 
         X509_free(client_cert);
     } else {
         debugs(83, 5, "clientNegotiateSSL: FD " << fd <<
                " has no certificate.");
     }
 
-    conn->readSomeData();
+    AsyncJob::Start(conn);
 }
 
 /**
  * If SSL_CTX is given, starts reading the SSL handshake.
  * Otherwise, calls switchToHttps to generate a dynamic SSL_CTX.
  */
 static void
 httpsEstablish(ConnStateData *connState,  SSL_CTX *sslContext, Ssl::BumpMode 
bumpMode)
 {
     SSL *ssl = NULL;
     assert(connState);
     const Comm::ConnectionPointer &details = connState->clientConnection;
 
     if (sslContext && !(ssl = httpsCreate(details, sslContext)))
         return;
 
     typedef CommCbMemFunT<ConnStateData, CommTimeoutCbParams> TimeoutDialer;
     AsyncCall::Pointer timeoutCall = JobCallback(33, 5, TimeoutDialer,
                                      connState, ConnStateData::requestTimeout);
     commSetConnTimeout(details, Config.Timeout.request, timeoutCall);

=== modified file 'src/client_side.h'
--- src/client_side.h   2014-07-12 09:34:43 +0000
+++ src/client_side.h   2014-07-12 16:01:22 +0000
@@ -313,40 +313,41 @@
      \param request   if it is not NULL also checks if the pinning info refers 
to the request client side HttpRequest
      \param CachePeer      if it is not NULL also check if the CachePeer is 
the pinning CachePeer
      \return          The details of the server side connection (may be closed 
if failures were present).
      */
     const Comm::ConnectionPointer validatePinnedConnection(HttpRequest 
*request, const CachePeer *peer);
     /**
      * returts the pinned CachePeer if exists, NULL otherwise
      */
     CachePeer *pinnedPeer() const {return pinning.peer;}
     bool pinnedAuth() const {return pinning.auth;}
 
     // pining related comm callbacks
     void clientPinnedConnectionClosed(const CommCloseCbParams &io);
 
     // comm callbacks
     void clientReadRequest(const CommIoCbParams &io);
     void connStateClosed(const CommCloseCbParams &io);
     void requestTimeout(const CommTimeoutCbParams &params);
 
     // AsyncJob API
+    virtual void start();
     virtual bool doneAll() const { return BodyProducer::doneAll() && false;}
     virtual void swanSong();
 
     /// Changes state so that we close the connection and quit after serving
     /// the client-side-detected error response instead of getting stuck.
     void quitAfterError(HttpRequest *request); // meant to be private
 
     /// The caller assumes responsibility for connection closure detection.
     void stopPinnedConnectionMonitoring();
 
 #if USE_OPENSSL
     /// called by FwdState when it is done bumping the server
     void httpsPeeked(Comm::ConnectionPointer serverConnection);
 
     /// Start to create dynamic SSL_CTX for host or uses static port SSL 
context.
     void getSslContextStart();
     /**
      * Done create dynamic ssl certificate.
      *
      * \param[in] isNew if generated certificate is new, so we need to add 
this certificate to storage.
@@ -382,40 +383,50 @@
 #endif
 
     /* clt_conn_tag=tag annotation access */
     const SBuf &connectionTag() const { return connectionTag_; }
     void connectionTag(const char *aTag) { connectionTag_ = aTag; }
 
 protected:
     void startDechunkingRequest();
     void finishDechunkingRequest(bool withSuccess);
     void abortChunkedRequestBody(const err_type error);
     err_type handleChunkedRequestBody(size_t &putSize);
 
     void startPinnedConnectionMonitoring();
     void clientPinnedConnectionRead(const CommIoCbParams &io);
 
 private:
     int connFinishedWithConn(int size);
     void clientAfterReadingRequests();
     bool concurrentRequestQueueFilled() const;
 
+    /* PROXY protocol functionality */
+    bool proxyProtocolValidateClient();
+    bool findProxyProtocolMagic();
+    bool parseProxy10();
+    bool parseProxy20();
+    bool proxyProtocolError(const char *reason = NULL);
+
+    /// whether PROXY protocol header is still expected
+    bool needProxyProtocolHeader_;
+
 #if USE_AUTH
     /// some user details that can be used to perform authentication on this 
connection
     Auth::UserRequest::Pointer auth_;
 #endif
 
     HttpParser parser_;
 
     // XXX: CBDATA plays with public/private and leaves the following 
'private' fields all public... :(
 
 #if USE_OPENSSL
     bool switchedToHttps_;
     /// The SSL server host name appears in CONNECT request or the server ip 
address for the intercepted requests
     String sslConnectHostOrIp; ///< The SSL server host name as passed in the 
CONNECT request
     String sslCommonName; ///< CN name for SSL certificate generation
     String sslBumpCertKey; ///< Key to use to store/retrieve generated 
certificate
 
     /// HTTPS server cert. fetching state for bump-ssl-server-first
     Ssl::ServerBump *sslServerBump;
     Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use
 #endif

Reply via email to