08-Dec-2022 02:21
Hello!
I have a requirement to match and log selected XML content under APM enabled VS
I tried to follow https://techdocs.f5.com/en-us/bigip-15-0-0/big-ip-local-traffic-manager-implementations/routing-base...
Here is a XML:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<soap:Header>
<t:RequestServerVersion Version="Exchange2007_SP1"/>
</soap:Header>
<soap:Body>
<m:GetFolder xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:FolderShape>
<t:BaseShape>IdOnly</t:BaseShape>
</m:FolderShape>
<m:FolderIds>
<t:DistinguishedFolderId Id="root"></t:DistinguishedFolderId>
</m:FolderIds>
</m:GetFolder>
</soap:Body>
</soap:Envelope>
Here is a XML profile:
ltm profile xml /Common/EWS_xml {
app-service none
defaults-from xml
namespace-mappings {
{
mapping-namespace http://schemas.microsoft.com/exchange/services/2006/messages
mapping-prefix m
}
}
xpath-queries { //m:getfolder/* }
}
Here is an iRule:
when XML_CONTENT_BASED_ROUTING priority 500 {
for {set i 0} { $i < $XML_count } {incr i} {
if {$static::iteco_exch_debug == 1} {
log local1.debug "APM: 0149FFFF:F: [ACCESS::session data get "session.user.sessionid"]: $XML_queries($i) = $XML_values($i)"
}
}
}
Unfortunately I miss something and there are no logs nor iRule event XML_CONTENT_BASED_ROUTING matches
Can you help me to understand what is wrong in my configuration?
Solved! Go to Solution.
08-Dec-2022 03:33
Hi akhmarov,
just wanted to get sure... 😉
Try to format your xpath-query case-sensitive... 😉
//m:GetFolder/*
Cheers, Kai
08-Dec-2022 02:46
Hi akhmarov,
I'm afraid to ask, but did you assigned the XML profile to your Virtual Server?
Cheers, Kai
08-Dec-2022 03:26
Yes, of course 🙂
ltm virtual /Common/EWS_vs {
profiles {
...
/Common/EWS_xml {
context all
}
...
}
}
08-Dec-2022 03:33
Hi akhmarov,
just wanted to get sure... 😉
Try to format your xpath-query case-sensitive... 😉
//m:GetFolder/*
Cheers, Kai
08-Dec-2022 03:35
Thanks! That helped
08-Dec-2022 03:39
You're welcome!
Cheers, Kai
08-Dec-2022 03:55
Kai, may I ask you how to construct XPath query to find each node starting with "m:"?
<m:GetFolder xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types">
<m:FolderShape>
...
</m:FolderShape>
<m:FolderIds>
...
</m:FolderIds>
</m:GetFolder>
I need to extract "GetFolder", "FolderShape" and "FolderIds" with their properties
08-Dec-2022 06:04
Seems that the answer to my question should be "NO" because paragraph Writing XPath queries from https://techdocs.f5.com/en-us/bigip-15-0-0/big-ip-local-traffic-manager-implementations/routing-base... states 4. Write queries to match elements and attributes
To catch something like GetFolder from my original XML example I tied to use:
local-name(//soap:Body/m:*)
name(//soap:Body/m:*)
//soap:Body/m:*
But the last XPath query was the only match found in logs
So I must consider that XML_CONTENT_BASED_ROUTING event cannot be used for XML node names extraction
Any help appreciated 🙂
08-Dec-2022 06:26 - edited 08-Dec-2022 07:05
Hi ahkmarov,
i used XPath expression tools in the past to write my queries. It helps a lot...
Free Online XPath Tester / Evaluator - FreeFormatter.com
You may change your queries to... (use both)
//m:GetFolder/m:FolderIds/*
//m:GetFolder/m:FolderShape/*
The XML and iRule you've provided would output this...
<XML_CONTENT_BASED_ROUTING>: //m:GetFolder/m:FolderShape/* = IdOnly
<XML_CONTENT_BASED_ROUTING>: //m:GetFolder/m:FolderIds/* =
Cheers, Kai
08-Dec-2022 07:35
Thanks!
I used http://xpather.com/ for tests
My issue is that I do not know exact XML node names, like GetFolder or FolderShape. I would like to iterate SOAP body and receive node names to analyze them
08-Dec-2022 07:45
So you need to check for yet unknown included node names other than GetFoler and FolderShape?
Cheers, Kai
08-Dec-2022 07:47
Exactly! And because there is a limit on 3 xpath queries I cannot iterate all EWS commands
08-Dec-2022 07:53
You would need to dig into the POST requests via [HTTP::collect]. Sounds like some heavy lifting...
Cheers, Kai
08-Dec-2022 07:54
Yep. I tried easier approach with XCBR, but unfortunately it cannot be done
Thanks!
08-Dec-2022 08:12 - edited 08-Dec-2022 08:13
Depending on what your exactly trying to achive, using [STREAM::*] may by an alternative to inspect incomming POST requests.
Cheers, Kai
08-Dec-2022 09:01
Much thank you for an idea
09-Dec-2022 02:14
You're welcome. If need further assistance with [STREAM::*] then let me know...
Cheers, Kai
09-Dec-2022 14:06
Yes, seems I need some assistance with STREAM
My setup uses Layered-VS scenario (LTM_VS forwarding to APM_VS with help of simple iRule). APM_VS is used for Exchange Server publication (EWS protocol mainly). LTM_VS has datasafe and stream profiles attached. There is another iRule also which is doing simple replace string1 with string2 under HTTP_REQUEST event (this iRule is attached to LTM_VS)
Seems that after enabling stream engine with STREAM::enable under HTTP_REQEUST any Content-Length header is replaced with Transfer-Encoding: chunked. According to F5 docs this is normal behavior. Unfortunately this setup breaks EWS protocol
I compared TCPDUMP captures from working and non-working scenario. And found that Exchange server could answer to authentication requests with Transfer-Encoding: chunked but will not answer to EWS SOAP payload after successful authentication. If EWS SOAP payload came with Content-Length header everything is fine
So my question is: how can I use stream profile and unchunk resulted HTTP packet to be sent to Exchange with Content-Length header instead of Transfer-Encoding: chunked?
I tried adding this iRule to APM_VS under HTTP_REQUEST but nothing changed. TCPDUMP showed that Transfer-Encoding: chunked is still present in the packet that goes to Exchange server
if {[HTTP::header exists "Transfer-Encoding"]} {
HTTP::header remove "Transfer-Encoding"
HTTP::payload unchunk
}
09-Dec-2022 20:56 - edited 09-Dec-2022 23:44
Hi Vladimir,
STREAM is designed to modify TCP::payload on a stream of "just enough" segments, to find and replace payload data.
This behavior may break the Content-Length mechanic of HTTP requests, if you're going to change the total length of the HTTP payload.
F5 added changes to STREAM / HTTP profile combination, so that classic Content-Length mechanic (length is included in the header) will be turned into a chunked "Transfer-Encoding" mechanic (length is included in each chunk), to support the STREAM'ing on "just enough" HTTP chunks for find and replace operations. In the case that chunked payload gets changed and resulting in a different chunk size, the length field of the currently processed chunk gets adjusted...
But yeah... you only want to log and not adjust anything, isn't it?
AFAIK did F5 not implemented a setting to explicitly turn off this behavior for STREAM / HTTP traffic. But you may clarify this with F5 Support to get sure...
If the behavior can't be turned off, then you may add a 3rd VS-Layer in your chain to STREAM process certain URIs.
VS_FronEnd -> VS_STREAM -> VS_APM (for specific request)
VS_FrontEnd -> VS_APM (for remaining Traffic)
The VS_STREAM will only get a TCP and STREAM Profile assigned, but not a HTTP Profile. Setup your expressions during CLIENT_ACCEPTED and then do some fancy stuff on STREAM_MATCHED events. But keep in mind to not change anything... just analyse the TCP stream and log your expressions...
Cheers, Kai
09-Dec-2022 23:38 - edited 09-Dec-2022 23:50
Hi Vladimir,
below is an intresting iRule Code to play with...
when RULE_INIT priority 1 {
set static::stream_expression "@</t:\[^>\]*>@@"
set static::already_known_xml_nodes ""
}
when CLIENT_ACCEPTED {
virtual VS_APM
STREAM::expression $static::stream_expression
STREAM::enable
}
when STREAM_MATCHED {
if { [STREAM::match] starts_with "</t:" } then {
set temp(node_name) [string range [STREAM::match] 2 end-1]
if { [lsearch $static::already_known_xml_nodes $temp(node_name)] == -1 } then {
log local0.debug "XML Parser: Learned a new node name \"$temp(node_name)\" on TMM=[TMM::cmp_unit]."
append static::stream_expression "@\<$temp(node_name).*\<\/$temp(node_name)\>@@"
lappend static::already_known_xml_nodes $temp(node_name)
} else {
# t: XML Node is already trained...
}
} else {
log local0.debug "XML Parser: [STREAM::match]"
}
STREAM::replace
}
The iRule analyses TCP payload via STREAM and searches for </t:nodename> strings. If a yet unknown </t:nodename> is detected, the iRule will update the STREAM::expression to include a regex pattern for <t:nodename>.*</t:nodemand>. On the very next TCP connection the iRule will start log the just learned XML node name...
<STREAM_MATCHED>: XML Parser: Learned a new node name "t:BaseShape" on TMM=0.
<STREAM_MATCHED>: XML Parser: Learned a new node name "t:DistinguishedFolderId" on TMM=0.
<STREAM_MATCHED>: XML Parser: Learned a new node name "t:BaseShape" on TMM=1.
<STREAM_MATCHED>: XML Parser: Learned a new node name "t:DistinguishedFolderId" on TMM=1.
<STREAM_MATCHED>: XML Parser: <t:BaseShape>IdOnly</t:BaseShape>
<STREAM_MATCHED>: XML Parser: <t:DistinguishedFolderId Id="root">1</t:DistinguishedFolderId>
<STREAM_MATCHED>: XML Parser: <t:BaseShape>IdOnly</t:BaseShape>
<STREAM_MATCHED>: XML Parser: <t:DistinguishedFolderId Id="root">1</t:DistinguishedFolderId>
Cheers, Kai
10-Dec-2022 01:31
Hi Kai
Thanks. Do I understand your idea correctly? Because you are using expandable static variable it is possible to access found XML node names using other iRules attached to another VS?
Do you have any ideas if we need to find&erase selected XML nodes in HTTP traffic using STREAM profile? And of course we need to return Content-Length header 🙂 Maybe I have to manually do:
1. Remove Transfer-Encoding header
2. Calculate HTTP payload length and insert Content-Length header
?
10-Dec-2022 02:47 - edited 10-Dec-2022 07:33
The static::variables are just a store for the STREAM expressions. They are maintained on each TMM core independently (for performance reasons) and adjusting the STREAM parsing over the time.
Its more or less just a gimmic to get it the expressions up an running without knowing the node names in advance. Once you figured out the names of the XML nodes you want to parse, you should change the logic to use fixed expression strings for STREAM.
To delete/change an entire [STREAM::match] your could call [STREAM::replace] with additional parameters.
iRule Command: STREAM::replace (f5.com)
You may forward results of the VS_STREAM to your VS_APM via the [sharedvar] command.
iRule Command: sharedvar (f5.com)
And unfortunately i dont have a working solution to unchunk a HTTP request before its hits your server.
Resolving the chunked encoding would need to [HTTP::collect] the entire request and parse the chunks from the first to the last chunk while stripping the length fields to restore the Content-Length mechanic.
It would be far easier to avoid the chunks to begin with and directly use [HTTP::collect] to parse your XML... 😉
Cheers, Kai