-
Notifications
You must be signed in to change notification settings - Fork 3
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
base: master
Are you sure you want to change the base?
Basic PROXY protocol support for Squid-to-peer connections #281
Conversation
src/proxyp/Header.cc
Outdated
ProxyProtocol::Header::Header(const SBuf &ver, const Two::Command cmd): | ||
version_(ver), | ||
command_(cmd), | ||
ignoreAddresses_(false) | ||
{} | ||
|
||
void | ||
ProxyProtocol::Header::packInto(MemBuf &mb) const |
There was a problem hiding this comment.
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:
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.
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.
There was a problem hiding this 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/FwdState.cc
Outdated
|
||
BinaryPacker packer; | ||
header.pack(packer); | ||
const auto packed = packer.packed(); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Also do not reset this field too early.
Also disclosed lack of support for multiple directives. We may need to relax that requirement later.
src/format/Format.cc
Outdated
Format::Format::hasPercentCode() const | ||
{ | ||
for (auto t = format; t; t = t->next) { | ||
if (t->type != LFT_STRING) |
There was a problem hiding this comment.
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).
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.h
Outdated
{ | ||
public: | ||
Option(const char *aName, const char *aVal, bool quoted); | ||
virtual ~Option() { delete valueFormat; } |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
{ | ||
char *key = nullptr; | ||
char *value = nullptr; | ||
if(!ConfigParser::NextKvPair(key, value)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
ProxyProtocol::OutgoingHttpConfig::parseOptions(ConfigParser &parser) | ||
{ | ||
// required options | ||
srcAddr = new AddrOption("src_addr", requiredValue("src_addr"), ConfigParser::LastTokenWasQuoted()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
} | ||
} | ||
|
||
static unsigned short |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static unsigned short | |
static uint16_t |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
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()); |
There was a problem hiding this comment.
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.
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".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
{ | ||
Parser::Tokenizer tok(val); | ||
int64_t p = -1; | ||
if (!tok.int64(p, 10, false) || (p > std::numeric_limits<uint16_t>::max())) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/proxyp/OutgoingHttpConfig.h
Outdated
Port port(const AccessLogEntryPointer &al) const; | ||
|
||
protected: | ||
Port port_; ///< parsed address (for options without logformat %macros) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Port port_; ///< parsed address (for options without logformat %macros) | |
Port port_; ///< transaction-independent source or destination address port |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
|
||
ProxyProtocol::AddrOption::AddrOption(const char *aName, const char *aVal, bool quoted) : Option(aName, aVal, quoted) | ||
{ | ||
if (!valueFormat || !valueFormat->hasPercentCode()) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/proxyp/OutgoingHttpConfig.h
Outdated
void dump(std::ostream &); | ||
|
||
SBuf theName; ///< Configured option name | ||
SBuf theValue; ///< Configured option value, possibly with %macros. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
void | ||
ProxyProtocol::Option::dump(std::ostream &os) | ||
{ | ||
os << theName << '=' << ConfigParser::QuoteString(SBufToString(theValue)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
valueFormat = new Format::Format(theName.c_str()); | ||
if (!valueFormat->parse(theValue.c_str())) { | ||
delete valueFormat; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
} | ||
} | ||
|
||
static unsigned short |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
{ | ||
Parser::Tokenizer tok(val); | ||
int64_t p = -1; | ||
if (!tok.int64(p, 10, false) || (p > std::numeric_limits<uint16_t>::max())) |
There was a problem hiding this comment.
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.
src/proxyp/OutgoingHttpConfig.cc
Outdated
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
auto p = srcPort->port(al); | ||
src.port(p ? *p : 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
ProxyProtocol::OutgoingHttpConfig::parseOptions(ConfigParser &parser) | ||
{ | ||
// required options | ||
srcAddr = new AddrOption("src_addr", requiredValue("src_addr"), ConfigParser::LastTokenWasQuoted()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.h
Outdated
{ | ||
public: | ||
Option(const char *aName, const char *aVal, bool quoted); | ||
virtual ~Option() { delete valueFormat; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.h
Outdated
Port port(const AccessLogEntryPointer &al) const; | ||
|
||
protected: | ||
Port port_; ///< parsed address (for options without logformat %macros) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
This complication is required to let admin configure Squid to withheld address or port information without generating cache.log warnings. Other minor polishing touches.
and adjusted Format::Format::dump() accordingly. No functionality changes.
Use Option::valueFormat to get both raw and formatted option values (the raw value is obtained via Token::print(ostream)).
There was a problem hiding this 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).
src/proxyp/OutgoingHttpConfig.h
Outdated
void dump(std::ostream &); | ||
|
||
SBuf theName; ///< Configured option name | ||
SBuf theValue; ///< Configured option value, possibly with %macros. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done (0f26c31).
src/proxyp/OutgoingHttpConfig.cc
Outdated
|
||
ProxyProtocol::AddrOption::AddrOption(const char *aName, const char *aVal, bool quoted) : Option(aName, aVal, quoted) | ||
{ | ||
if (!valueFormat || !valueFormat->hasPercentCode()) |
There was a problem hiding this comment.
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.
src/proxyp/OutgoingHttpConfig.cc
Outdated
{ | ||
char *key = nullptr; | ||
char *value = nullptr; | ||
if(!ConfigParser::NextKvPair(key, value)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/proxyp/OutgoingHttpConfig.cc
Outdated
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()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/format/Format.cc
Outdated
Format::Format::hasPercentCode() const | ||
{ | ||
for (auto t = format; t; t = t->next) { | ||
if (t->type != LFT_STRING) |
There was a problem hiding this comment.
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
No description provided.