Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic PROXY protocol support for Squid-to-peer connections #281

Open
wants to merge 44 commits into
base: master
Choose a base branch
from

Conversation

eduard-bagdasaryan
Copy link

No description provided.

src/FwdState.cc Outdated Show resolved Hide resolved
ProxyProtocol::Header::Header(const SBuf &ver, const Two::Command cmd):
version_(ver),
command_(cmd),
ignoreAddresses_(false)
{}

void
ProxyProtocol::Header::packInto(MemBuf &mb) const

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this code survives, it should become:

Suggested change
ProxyProtocol::Header::packInto(MemBuf &mb) const
ProxyProtocol::Header::packInto(Packable &out) const

However, for now, please continue to use MemBuf (and do not focus on the quality of this packing code -- as long as it produces the right serialization/bytes-on-the-wire). I am investigating a possible replacement/upgrade, but that will take some time, and there is no good reason to block your progress on that.

src/proxyp/Header.cc Outdated Show resolved Hide resolved
src/proxyp/Header.h Outdated Show resolved Hide resolved
src/proxyp/Header.h Outdated Show resolved Hide resolved
This implementation is still untested. I also plan to remove the failed
attempt at (hopefully premature) optimization to simplify further.

This commit should fix PROXY protocol "tail" length packing.

Also added code to pack TLVs. TLVs are currently absent, but
ProxyProtocol::Header::pack() should not know that.
If we ever implement locking (removed in previous commit), this position
will also help to "automatically" check the lock before any object
updates.

Also fixed method const-correctness.
Copy link

@rousskov rousskov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also fix "make check" when you get a chance.

src/proxyp/Header.cc Outdated Show resolved Hide resolved
src/FwdState.cc Outdated Show resolved Hide resolved
src/FwdState.cc Outdated

BinaryPacker packer;
header.pack(packer);
const auto packed = packer.packed();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you update this code to generate the PROXY protocol header before opening a connection, please store it as SBuf. Convert to MemBuf only when it is time to write it (using the freshly opened connection).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Format::Format::hasPercentCode() const
{
for (auto t = format; t; t = t->next) {
if (t->type != LFT_STRING)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to handle LFT_PERCENT specially, right? And LFT_NONE AFAICT (e.g., for a logformat that is an empty string).

Suggested change
if (t->type != LFT_STRING)
if (t->type != LFT_NONE && t->type != LFT_STRING && t->type != LFT_PERCENT) // other codes require ALE

... but the above is also inaccurate because %byte does not require ALE. We need to decide whether this method is really "this logformat has a logformat %code" or "this logformat requires ALE" to finalize this.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several other codes not requiring ALE, such as LFT_TIME_SECONDS_SINCE_EPOCH, LFT_TIME_SUBSECOND, LFT_SEQUENCE_NUMBER. We can check them all in a similar method (e.g., requiresALE()) but how are we going to maintain this condition, i.e., if some of these codes start using ALE or others stop? At least we probably need adding some Format::Token::requiresAle flag and assert in Format::assemble() if the flag does not match the passed ALE pointer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several other codes not requiring ALE, such as LFT_TIME_SECONDS_SINCE_EPOCH, LFT_TIME_SUBSECOND, LFT_SEQUENCE_NUMBER. We can check them all in a similar method (e.g., requiresALE())

Let's move from hasPercentCode() to isConstant():

  • Do not include LFT_TIME_SECONDS_SINCE_EPOCH and LFT_TIME_SUBSECOND. They will change with time and, depending on formatting, those changes may affect the validity of port numbers or even IP addresses produced using those %codes.
  • Do not include LFT_SEQUENCE_NUMBER. It will change from one transaction to another, and, depending on formatting, those changes may affect the validity of port numbers or even IP addresses produced using that %code.

but how are we going to maintain this condition, i.e., if some of these codes start using ALE or others stop?

It is the right question to ask, but let's not solve that problem in this PR. A proper solution requires significant out-of-scope Format refactoring, and I am not even sure it is worth doing because, after all, we are talking about a feature that may be called an "optimization" -- we want to detect misconfigurations earlier.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (e045f61).

void
ProxyProtocol::OutgoingHttpConfig::fill(ProxyProtocol::Header &header, const AccessLogEntryPointer &al)
{
fillAddresses(header.sourceAddress, header.destinationAddress, al);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are guaranteed to have ALE here, we should at least assert that (but it may be better to change the parameter type from a pointer to a reference). Otherwise, we should not call assemble() (down the stack) without ALE because that method (at least) assumes that ALE is available AFAICT.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
public:
Option(const char *aName, const char *aVal, bool quoted);
virtual ~Option() { delete valueFormat; }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please try to avoid including format/Format.h into this header by moving destructor definition to .cc file (and including format/forward.h instead).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
char *key = nullptr;
char *value = nullptr;
if(!ConfigParser::NextKvPair(key, value))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use optionalKvPair() if possible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

ProxyProtocol::OutgoingHttpConfig::parseOptions(ConfigParser &parser)
{
// required options
srcAddr = new AddrOption("src_addr", requiredValue("src_addr"), ConfigParser::LastTokenWasQuoted());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, please use parser.Foo() instead of ConfigParser::Foo(). We want to get rid of LegacyParser global (eventually), but I do not recall whether parser.Foo() produces warnings for static Foo() methods. If it does trigger warnings, then "if possible" precondition cannot be satisfied, of course.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

static unsigned short

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static unsigned short
static uint16_t

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Parser::Tokenizer tok(val);
int64_t p = -1;
if (!tok.int64(p, 10, false) || (p > std::numeric_limits<uint16_t>::max()))
throw TextException(ToSBuf("Could not parse '", val, "' as an IP port"), Here());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IP (i.e. the protocol) has no ports.

Suggested change
throw TextException(ToSBuf("Could not parse '", val, "' as an IP port"), Here());
throw TextException(ToSBuf("Could not parse '", val, "' as an address port"), Here());

... but we may be able to improve this wording by making this function a class method, to gain access to the option "name".

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
Parser::Tokenizer tok(val);
int64_t p = -1;
if (!tok.int64(p, 10, false) || (p > std::numeric_limits<uint16_t>::max()))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get better errors and improve code quality, please use udec64() instead of int64(), making p constant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not use it because udec64() throws InsufficientInput() in our case.

Port port(const AccessLogEntryPointer &al) const;

protected:
Port port_; ///< parsed address (for options without logformat %macros)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Port port_; ///< parsed address (for options without logformat %macros)
Port port_; ///< transaction-independent source or destination address port

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


ProxyProtocol::AddrOption::AddrOption(const char *aName, const char *aVal, bool quoted) : Option(aName, aVal, quoted)
{
if (!valueFormat || !valueFormat->hasPercentCode())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see if we can refactor so that we do not store valueFormat when it is used only once (here). Same for storing theValue when valueFormat has %codes, actually. In other words, try to avoid storing anything we are not going to use after construction.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need valueFormat after addressing another request, that removed the raw theValue field.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need valueFormat

I changed this after addressing the new requirement to treat "-" as as an 'undefined' value. In this special case valueFormat is not created.

void dump(std::ostream &);

SBuf theName; ///< Configured option name
SBuf theValue; ///< Configured option value, possibly with %macros.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about complexities in current PR code a bit more, I realized that we should make one significant simplification: There should be only one Option::value data member. It's type should be [a pointer to] Format. The specs should be adjusted to always interpret configured option values as logformat strings, regardless of value quoting (but surrounding quotes, if any, should be automatically removed) and regardless of whether the value contains %codes.

If an admin configures a constant value (i.e. a value without %codes), it is up to Format class to decide whether to optimize handling of that constant logformat value. This PR code has nothing to do with that optional optimization; it is Format business.

Format::dump() can be used to obtain raw/uncompiled logformat value where needed (e.g., for dumping new directive configuration itself).

I believe the above ideas supersede any conflicting suggestions in the previous review.


This PR code should still reject configurations where

  • "constant" address values are malformed or have mismatching families
  • "constant" port values are malformed
  • two TLVs are identical

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Format::dump() can be used to obtain raw/uncompiled logformat value where needed

Format::Format::dump(StoreEntry * entry,...) is incompatible with our Configuration::Component<ProxyProtocol::OutgoingHttpConfig*>::Print(std::ostream &os, ...). Do you suggest refactoring Format::Format::dump() here to provide this compatibility?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I do not. I would probably start by adding Token::print(ostream) (to dump the format value; all tokens in the invasive list) and use that to implement the inner loop inside existing Format::dump(). PROXY protocol call can then print Format::format member using Token::print().

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (0f26c31).

src/cf.data.pre Outdated
configuration errors.

Quoted (using "double quotes") header field values are supported. Squid
removes quotes before sending the field value. Quoted header field values

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
removes quotes before sending the field value. Quoted header field values
removes quotes before using the field value.
A header field value, whether quoted or not,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Author

@eduard-bagdasaryan eduard-bagdasaryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially done (50096e6).

src/cf.data.pre Outdated
configuration errors.

Quoted (using "double quotes") header field values are supported. Squid
removes quotes before sending the field value. Quoted header field values
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

void
ProxyProtocol::Option::dump(std::ostream &os)
{
os << theName << '=' << ConfigParser::QuoteString(SBufToString(theValue));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 41 to 43
valueFormat = new Format::Format(theName.c_str());
if (!valueFormat->parse(theValue.c_str())) {
delete valueFormat;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}
}

static unsigned short
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
Parser::Tokenizer tok(val);
int64_t p = -1;
if (!tok.int64(p, 10, false) || (p > std::numeric_limits<uint16_t>::max()))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could not use it because udec64() throws InsufficientInput() in our case.

Comment on lines 163 to 173
srcAddr->dump(os);
os << separator;
dstAddr->dump(os);
os << separator;
srcPort->dump(os);
os << separator;
dstPort->dump(os);
for (const auto &t : tlvOptions) {
os << separator;
t->dump(os);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 199 to 200
auto p = srcPort->port(al);
src.port(p ? *p : 0);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

ProxyProtocol::OutgoingHttpConfig::parseOptions(ConfigParser &parser)
{
// required options
srcAddr = new AddrOption("src_addr", requiredValue("src_addr"), ConfigParser::LastTokenWasQuoted());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

{
public:
Option(const char *aName, const char *aVal, bool quoted);
virtual ~Option() { delete valueFormat; }
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Port port(const AccessLogEntryPointer &al) const;

protected:
Port port_; ///< parsed address (for options without logformat %macros)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Author

@eduard-bagdasaryan eduard-bagdasaryan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (up to e045f61).

void dump(std::ostream &);

SBuf theName; ///< Configured option name
SBuf theValue; ///< Configured option value, possibly with %macros.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (0f26c31).


ProxyProtocol::AddrOption::AddrOption(const char *aName, const char *aVal, bool quoted) : Option(aName, aVal, quoted)
{
if (!valueFormat || !valueFormat->hasPercentCode())
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need valueFormat after addressing another request, that removed the raw theValue field.

{
char *key = nullptr;
char *value = nullptr;
if(!ConfigParser::NextKvPair(key, value))
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 281 to 284
srcAddr = new AddrOption("src_addr", requiredValue("src_addr"), ConfigParser::LastTokenWasQuoted());
dstAddr = new AddrOption("dst_addr", requiredValue("dst_addr"), ConfigParser::LastTokenWasQuoted());
srcPort = new PortOption("src_addr", requiredValue("src_port"), ConfigParser::LastTokenWasQuoted());
dstPort = new PortOption("dst_addr", requiredValue("dst_port"), ConfigParser::LastTokenWasQuoted());
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Format::Format::hasPercentCode() const
{
for (auto t = format; t; t = t->next) {
if (t->type != LFT_STRING)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (e045f61).

having same type values but configured in decimal and hex
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants