Skip to content

Added ACL for checking CONNMARK#111

Merged
rousskov merged 7 commits intosquid-cache:masterfrom
k0rv1n:connmark_acl_3
Jan 16, 2018
Merged

Added ACL for checking CONNMARK#111
rousskov merged 7 commits intosquid-cache:masterfrom
k0rv1n:connmark_acl_3

Conversation

@k0rv1n
Copy link
Contributor

@k0rv1n k0rv1n commented Dec 21, 2017

Reworked #108.

@squid-prbot
Copy link
Collaborator

Can one of the admins verify this patch?

@yadij
Copy link
Contributor

yadij commented Dec 22, 2017

Can you please stop opening new PR for every little change. It should be sufficient to rebase your branch and re-push to update the initial PR. That way we get a branch trace of what was changed for audit request or elsewise, and don't have to manually schedule the QA testing.

@yadij
Copy link
Contributor

yadij commented Dec 22, 2017

OK to test

* Please see the COPYING and CONTRIBUTORS files for details.
*/


Copy link
Contributor

Choose a reason for hiding this comment

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

extra line still present, but I'm going to run the formatter on this to check for tab issues anyway so no need to hold the merge.

src/cf.data.pre Outdated
# NOTE 2: IPv6 protocol does not contain ARP. MAC/EUI is either
# encoded directly in the IPv6 address or not available.

acl aclname connmark mark[/mask] ...
Copy link
Contributor

Choose a reason for hiding this comment

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

AFAICT, the new code can only match markings on the client-Squid connections. If markings are possible on Squid-origin/peer connections as well, then we need a different name for this new ACL, right? Please note that it should not matter whether those Squid-server connection markings are set by Squid itself or something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe clientside_connmark or client_connmark will be a better option?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use the clientside_ prefix for consistency with the existing clientside_mark and clientside_tos directives. My vote goes for clientside_mark because I see no compelling reason to spell the ACL name slightly differently than the matching directive name.

None of these names are perfect because "client side" is an overloaded/abused term in Squid context (that we try to shy away from in recent code) and because "mark" is easily confused with "tag" and "annotation". However, I have nothing better to offer in this issue scope.

Please use clientside_mark for the new ACL name.

Copy link
Contributor

@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.

I found a few problems and added a few polishing requests. Please see inlined comments for details.

src/cf.data.pre Outdated
# NOTE 2: IPv6 protocol does not contain ARP. MAC/EUI is either
# encoded directly in the IPv6 address or not available.

acl aclname connmark mark[/mask] ...
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use the clientside_ prefix for consistency with the existing clientside_mark and clientside_tos directives. My vote goes for clientside_mark because I see no compelling reason to spell the ACL name slightly differently than the matching directive name.

None of these names are perfect because "client side" is an overloaded/abused term in Squid context (that we try to shy away from in recent code) and because "mark" is easily confused with "tag" and "annotation". However, I have nothing better to offer in this issue scope.

Please use clientside_mark for the new ACL name.

_mark = static_cast<nfmark_t>(tmp_result);
else {
debugs(28, DBG_CRITICAL, MYNAME << ": bad mark '" << token << "'");
self_destruct();
Copy link
Contributor

Choose a reason for hiding this comment

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

When handling errors in any new parsing code, please throw a TexcHere() exception instead of killing Squid with self_destruct(). We do not want to kill the Squid process during hot reconfiguration, and we will eventually support atomic rejection of invalid configurations (i.e., configurations that throw).

Please note that you do not need to catch and report the exception. There is already code for that (and there are pending projects that work on improving that code). Your patched Squid may still die when you throw instead of self_destruct()ing, but that unfortunate side effect is temporary, and you do not need to worry about it as long as you throw correctly.

continue;
}

nfmark_t _mask = 0xffffffff;
Copy link
Contributor

Choose a reason for hiding this comment

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

In Squid code, please do not start names with an underscore.

nfmark_t _mark = 0;

Parser::Tokenizer tokenizer(token);
int64_t tmp_result;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use camelCase for names in Squid code. Here, I suggest calling the result storage number, but it is your call.

src/cf.data.pre Outdated
acl aclname connmark mark[/mask] ...
# [fast]
# check CONNMARK
# mark and mask - hex or decimal values
Copy link
Contributor

Choose a reason for hiding this comment

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

The "hex or decimal" description does not match the parsing code which accepts octal values as well.

*/
enum ConnType {
Incoming,
Outgoing
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid the terms "incoming" and "outgoing" because a packet sent by Squid to the client is treated as an "outgoing" packet in some contexts. It would be awkward to deal with outgoing packets on incoming connections :-).

It is a good idea to use a common prefix for enum values, especially when they are placed in a relatively large (and mostly unrelated!) context like Qos. There is a good change this enum will be moved to Connection.h (or similar) later.

See another comment for specific suggestions (but you may be able to find better prefixes/names).

* @param conn_type Specifies connection type (incoming or outgoing)
*/
void getNfmarkFromServer(const Comm::ConnectionPointer &server, const fde *clientFde);
nfmark_t getNfmarkFromConnection(const Comm::ConnectionPointer &conn, enum ConnType conn_type = Outgoing);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please remove the default parameter value. When there is no reasonable default, it is a good idea to make the caller think what it actually wants.

}

if (marks.empty()) {
debugs(28, DBG_CRITICAL, MYNAME << ": expect at least one connmark");
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use typeString() instead of MYNAME -- MYNAME is expanded into useless-in-this-context parse. I would also add an "acl" prefix:

throw TexcHere(ToSBuf("acl ", typeString(), " requires at least one mark"));

Please check other new error reporting code for similar problems.

Eventually, context reporting will be handled by the upper level parser, but we are not there yet.

return;
}

newConnDetails->nfmark = Ip::Qos::getNfmarkFromConnection(newConnDetails, Ip::Qos::Incoming);
Copy link
Contributor

Choose a reason for hiding this comment

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

This adds performance overhead for all accepted connections even if the admin is not using the new ACL, right? How expensive are the associated system calls? Would it be easy to bypass this assignment when the new ACL is not used?

Similar concern applies to the FwdState code, but we were extracting the possibly useless mark there before your changes so I cannot argue a performance regression there.

if (x == -1) {
debugs(17, 2, "QOS: Failed to retrieve connection mark: (" << x << ") " << strerror(errno)
<< " (Destination " << server->remote << ", source " << server->local << ")" );
int xerrno = errno;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add const.

@k0rv1n
Copy link
Contributor Author

k0rv1n commented Dec 31, 2017

Happy New Year, guys. I`ll commit changes at 8-9 of January.

@rousskov
Copy link
Contributor

I suspect something went wrong when you tried to incorporate master changes into your feature branch -- the feature branch now shows 2K changed files and a few conflicts. FWIW, in most solo-developer cases, the best strategy is to rebase your changes on top of master (instead of merging master into your branch).

Copy link
Contributor

@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.

The code looks much better, thank you.

I added a few suggestions based on the last commit alone, but I cannot easily review the whole thing due to master-merge problems that I mentioned a bit earlier.

debugs(28, DBG_CRITICAL, MYNAME << ": bad mask '" << token << "'");
self_destruct();
if (!tokenizer.atEnd()) {
if (tokenizer.skip('/')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please simplify by moving the slash check up and collapsing the two atEnd() checks into one:

if (tokenizer.skip('/') {
    ... parse the mask ...
}

if (!tokenizer.atEnd())
    throw TextException(ToSBuf("acl ", typeString(), ": trailing garbage in ", token), Here());

if (!tokenizer.atEnd()) {
if (tokenizer.skip('/')) {
if (tokenizer.int64(number, 0, false))
mask = static_cast<nfmark_t>(number);
Copy link
Contributor

Choose a reason for hiding this comment

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

AFAICT, this code silently ignores user input due to a 64- to 32-bit conversion. Before you cast, add an overflow check. For example:

if (number > std::numeric_limits<nfmark_t>::max())
    throw ... too big

BTW, you may want to encapsulate all those repeated number parsing and checks into a helper function, but I do not insist on that.

SBuf s;
s.Printf("0x%08x/0x%08x", m.first, m.second);
s.Printf("0x%08x", m.first);
if (m.second)
Copy link
Contributor

Choose a reason for hiding this comment

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

This condition does not look right because the default mask (i.e., the one that should not be printed) is 0xffffffff, not zero.

for (const auto &m : marks) {
SBuf s;
s.Printf("0x%08x/0x%08x", m.first, m.second);
s.Printf("0x%08x", m.first);
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect you will find writing good debugging statements, exceptions, and this dump() code easier if you add an operator to print std::pair<nfmark_t, nfmark_t> into std::ostream!

Copy link
Contributor

@yadij yadij left a comment

Choose a reason for hiding this comment

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

just some polishing to do. overall it looks good to me now.

#include <string>

bool
Acl::ConnMark::empty () const
Copy link
Contributor

Choose a reason for hiding this comment

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

please remove the extra space between method name and brackets

#include "Parsing.h"
#include "sbuf/Stream.h"

#include <string>
Copy link
Contributor

Choose a reason for hiding this comment

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

Please consider removing, this should no longer be necessary with the SBuf changes.

src/cf.data.pre Outdated
# The client, various intermediaries, and Squid itself may set
# CONNMARK at various times. The last CONNMARK set wins. However,
# this ACL only checks the mark present on a freshly accepted
# connection, before clientside_mark is checked.
Copy link
Contributor

Choose a reason for hiding this comment

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

That last sentence is incorrect. This ACL is based on the Comm::Connection stored mark. Which may have been updated already on a persistent connection by a previous HTTP request use of clientside_mark, or by the directive using the ACL happening after clientside_mark for the current transaction. eg http_access uses the previous transaction (or client original) marking, whereas logging for the same transaction uses the clientside_mark changed value.

The most that can be said is that this ACL checks for the most recent CONNMARK used on the client connection.

Copy link
Contributor

Choose a reason for hiding this comment

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

That last sentence is incorrect.

Agreed. I probably missed setSockNfmark() calls when drafting it. Sorry.

The most that can be said is that this ACL checks for the most recent CONNMARK used on the client connection.

That does not tell the admin what they need to know IMO: It is important to warn the admin that the mark they are testing at time X may not match the actual/real connection mark at that time. How about the following?

	  # The client, various intermediaries, and Squid itself may set
	  # CONNMARK at various times. The last CONNMARK set wins. This ACL
	  # checks the mark present on an accepted connection or set by
	  # Squid afterwards, depending on the ACL check timing. This ACL
	  # effectively ignores any mark set by other agents after Squid has
	  # accepted the connection.

Copy link
Contributor

@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.

Great progress, thank you! I agree that we are almost done, but I found one bug (ConnMark::marks type) so you will have to endure another polishing round.

operator <<(std::ostream &os, const std::pair<nfmark_t,nfmark_t> connmark)
{
using namespace std;
ios_base::fmtflags oldFlags = os.flags();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make it const. I suggest a simpler version:

const auto oldFlags = os.flags();

BTW, after the above change, consider removing using namespace std; because there will be only two case of explicit namespace use: std::hex and std::showbase.

return false;
}

inline std::ostream &
Copy link
Contributor

Choose a reason for hiding this comment

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

s/inline/static/ as more precise -- we do not really care whether the compiler inlines this operator.

nfmark_t mark = 0;
nfmark_t mask = 0xffffffff;

mark = getNumber(tokenizer, token);
Copy link
Contributor

Choose a reason for hiding this comment

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

Please make mark constant:

const auto mark = getNumber(tokenizer, token);

throw TexcHere(ToSBuf("acl ", typeString(), ": trailing garbage in ", token));
}

std::pair<nfmark_t, nfmark_t> connmark(mark, mask);
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, make connmark constant.

Acl::ConnMark::match(ACLChecklist *cl)
{
const auto *checklist = Filled(cl);
nfmark_t connMark = checklist->conn()->clientConnection->nfmark;
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, make connMark constant. You can use const auto here AFAICT.

Also, please use either connMark or connmark for consistency sake. I recommend the former.

* src = client address(remote), dst = our address(local)
*/
auto dst = conn->remote;
auto src = conn->local;
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, make these constant.

Please use one-line initialization and removing the comment about "swapping" as no longer necessary:

const auto src = (connDir == Ip::Qos::dirAccepted) ? conn->remote : conn->local;
const auto dst = (connDir == Ip::Qos::dirAccepted) ? conn->local : conn->remote;


if (marks.empty()) {
throw TexcHere(ToSBuf("acl ", typeString(), " requires at least one mark"));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI: You do not need curly braces around simple, one-line statements like this. I do not insist on removing them. Just mentioning this here in case you do not like them either but think they should be there for some vague reason satisfying the official code "style"...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

left as is

}

nfmark_t
Acl::ConnMark::getNumber(Parser::Tokenizer &tokenizer, const SBuf &token)
Copy link
Contributor

Choose a reason for hiding this comment

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

If possible, please make this method const.

fde *clientFde = (fde *)data;
clientFde->nfmarkFromServer = nfct_get_attr_u32(ct, ATTR_MARK);
debugs(17, 3, "QOS: Retrieved connection mark value: " << clientFde->nfmarkFromServer);
nfmark_t *mark = static_cast<nfmark_t *>(connmark);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use auto to reduce code duplication:

auto mark = static_cast<nfmark_t *>(connmark);


os << hex << showbase << connmark.first;
if (connmark.second != 0xffffffff) {
os << "/" << connmark.second;
Copy link
Contributor

Choose a reason for hiding this comment

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

Use single quotes to print single characters -- it could be much faster and may help the compiler to inline more: '/'.

@k0rv1n k0rv1n force-pushed the connmark_acl_3 branch 2 times, most recently from 5c655df to ba277bb Compare January 15, 2018 07:53
@rousskov
Copy link
Contributor

@k0rv1n, I took the liberty to push a few minor fixes and polishing updates. See the branch change log for details -- I isolated and documented every change. I also formatted and rebased the branch. The latter required a force push. If you are OK with these changes (and the tests pass), your branch will be merged. Otherwise, we can revert the branch to its original state.

@rousskov
Copy link
Contributor

I wonder whether Squid should use uppercase for printing these hex numbers? We should probably use whichever case folks are more likely to use in their configuration files. I do not know what that is. Here is the current debugging sample:

ConnMark.cc(65) parse: added 0xabc/0xa
ConnMark.cc(65) parse: added 0xabcd/0xb
ConnMark.cc(84) match: skipped 0xabc/0xa mismatching 0
ConnMark.cc(84) match: skipped 0xabcd/0xb mismatching 0
ConnMark.cc(84) match: skipped 0xabc/0xa mismatching 0
ConnMark.cc(84) match: skipped 0xabcd/0xb mismatching 0
ConnMark.cc(84) match: skipped 0xabc/0xa mismatching 0x12
ConnMark.cc(84) match: skipped 0xabcd/0xb mismatching 0x12

TODO: We should probably convert the old code printing nfmarks (and ToS marks?) to use asHex() so that all debugging/reporting is consistent. This can be done as a separate polishing PR IMO.

@k0rv1n
Copy link
Contributor Author

k0rv1n commented Jan 16, 2018

I don't mind merging this. Thank you.

@rousskov rousskov merged commit 653d992 into squid-cache:master Jan 16, 2018
@rousskov
Copy link
Contributor

@k0rv1n, thank you and, if GitHub is not misleading me, congratulations on your first official Squid commit!

Do you know whether it is customary to enter CONNMARKs using uppercase (0xABCD) or lowercase (0xabcd) hex values?

@k0rv1n
Copy link
Contributor Author

k0rv1n commented Jan 17, 2018

I suppose that it would be preferable to use lowercase marks.

@k0rv1n k0rv1n deleted the connmark_acl_3 branch January 17, 2018 09:20
squidadm pushed a commit to squidadm/squid that referenced this pull request Jan 19, 2018
Matches CONNMARK of accepted connections. Takes into account
clientside_mark and qos_flows mark changes (because Squid-set marks are
cached by Squid in conn->nfmark). Ignores 3rd-party marks set after
Squid has accepted the connection from a client (because Squid never
re-queries the connection to update/sync conn->nfmark).

Also added a debugs()-friendly API to print hex values.
squidadm pushed a commit to squidadm/squid that referenced this pull request Jan 19, 2018
Matches CONNMARK of accepted connections. Takes into account
clientside_mark and qos_flows mark changes (because Squid-set marks are
cached by Squid in conn->nfmark). Ignores 3rd-party marks set after
Squid has accepted the connection from a client (because Squid never
re-queries the connection to update/sync conn->nfmark).

Also added a debugs()-friendly API to print hex values.
yadij pushed a commit that referenced this pull request Jan 19, 2018
Matches CONNMARK of accepted connections. Takes into account
clientside_mark and qos_flows mark changes (because Squid-set marks are
cached by Squid in conn->nfmark). Ignores 3rd-party marks set after
Squid has accepted the connection from a client (because Squid never
re-queries the connection to update/sync conn->nfmark).

Also added a debugs()-friendly API to print hex values.
@yadij
Copy link
Contributor

yadij commented Jan 23, 2018

IMO we should accept either case on input, but print in upper case when displaying so the lower case 'x' is more quickly/clearly visible.

@rousskov
Copy link
Contributor

Agreed regarding accepting both cases and regarding upper case resulting in a more readable text after the 0x prefix. I would be happy to change code to use upper case, but I am worried that (AFAICT) @k0rv1n thought that most admins would use lower case in squid.conf.

It would be nice if there were some "standard" CONNMARK rendering that we could point to as the authoritative "norm". How do various Unix tools render CONNMARKs? What case does their configuration documentation use?

@yadij
Copy link
Contributor

yadij commented Jan 24, 2018

Netfilter documentation uses decimal numbers.

Iptables tools produce lower-case hex, but for those tools all the output line is universally lower cased. Even names that are officially defined as being upper-case by netfilter.

@yadij
Copy link
Contributor

yadij commented Jan 24, 2018

It is not a big issue either way, if the patch is ready it can be merged.

@rousskov
Copy link
Contributor

This PR was merged a week ago.

It is not too late to change the (default) asHex() rendering case to uppercase though. I just wish there would be a more clear indication of what the right choice is here, but it sounds like everybody is doing their own thing :-(.

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.

4 participants