Mark Stout
T-Mobile
Using wireshark in LTE, and 5G networks…
#sf25us
About me?
#sf25us
UE
eNodeB
eNodeB
MME
S-GW
P-GW
HSS
PCRF
Uu
Uu
X2
S1-U
Enhanced Packet Core (EPC)
S11
S6a
S5/S8
Gx
SGi
Internet
E-UTRAN
We will concentrate on packets entering, exiting, and within the highlighted portion.
S1-MME
UE User Equipment, Users phone, or device
eNodeB Radio Access side of the network
MME Local Mobility Management Entity. Only handles signaling for call setup, and certain hand offs
S-GW Serving Gateway. Mobility anchor point. Handles signaling, and data plane
P-GW PDN-GW. Packet Data Network Gateway. Provides IP, and session connectivity(UE Anchor).
HSS Home Subscriber Server. Houses subscriber related information, and provides Authentication, and Authorization.
PCRF Policy and Charging Function.
**Certain nodes are not represented for simplicity
#sf25us
Protocols we’re covering
MME
S-GW
P-GW
HSS
PCRF
S1-U
S11
S6a
S5/S8
Gx
SGi
Internet
S1-MME
S1ap
GTP-C
GTP-C
GTP-U
GTP-U
Diameter
Diameter
SCTP
SCTP
UDP
UDP
UDP
TCP
#sf25us
SCTP �Stream Control Transmission Protocol RFC4960
#sf25us
Standard header
#sf25us
SCTP �Stream Control Transmission Protocol RFC4960
#sf25us
Redundancy built in
Multihoming can be established at time of init ack.
This is how we dynamically build redundant route from Cell Site, to MME.
Redundant route start heart beat.
#sf25us
Initiation (INIT)
9
#sf25us
Initiation Acknowledgement (INITACK)
10
#sf25us
Cookie Echo (COOKIE ECHO)
11
#sf25us
SCTP �Stream Control Transmission Protocol RFC4960
#sf25us
Selective Acknowledgement (SACK)
13
#sf25us
SCTP �Stream Control Transmission Protocol RFC4960
#sf25us
Selective Acknowledgement (SACK)
15
#sf25us
SCTP profile
#sf25us
Diameter
#sf25us
Diameter
#sf25us
Diameter
Peering State Machine
#sf25us
Diameter
All Diameter headers start with the same 20 bytes. After that AVP’s are added
Diameter Header information
#sf25us
Diameter
#sf25us
Diameter
#sf25us
Diameter profile
#sf25us
Diameter profile
Dictionary.xml is the main file, and is responsible for calling all the other specific files
<avp name="Vendor-Id" code="266" mandatory="must" may-encrypt="no" protected="may" vendor-bit="mustnot">
<type type-name="VendorId"/>
</avp>
<avp name="Firmware-Revision" code="267" mandatory="mustnot" protected="mustnot" may-encrypt="no" vendor-bit="mustnot">
<type type-name="Unsigned32"/>
</avp>
<avp name="Result-Code" code="268" mandatory="must" may-encrypt="no" protected="mustnot" vendor-bit="mustnot">
<type type-name="Enumerated"/>
<enum name="DIAMETER_MULTI_ROUND_AUTH" code="1001"/>
<enum name="DIAMETER_SUCCESS" code="2001"/>
#sf25us
Diameter profile
<avp name="Charging-Rule-Name" code="1005" mandatory="must" may-encrypt="yes" protected="may" vendor-bit="must" vendor-id="TGPP">
<type type-name="OctetString"/>
</avp>
<avp name="Event-Trigger" code="1006" mandatory="must" may-encrypt="yes" protected="may" vendor-bit="must" vendor-id="TGPP">
<type type-name="Enumerated"/>
<enum name="SGSN_CHANGE" code="0"/>
<enum name="QOS_CHANGE" code="1"/>
<enum name="RAT_CHANGE" code="2"/>
<enum name="TFT_CHANGE" code="3"/>
<enum name="PLMN_CHANGE" code="4"/>
<enum name="LOSS_OF_BEARER" code="5"/>
</avp>
In this example <avp starts the a new description
name= defines the name that will show in wireshark for the code= defined
type-name=“Enumerated = a number value that will later get a name assigned to the value
OctetString = A string of octets ie. 0a ff cd bb
UTF8String = text
if “Enumerated” is defined then <enum name=“what ever” code=“0”/> associates a string with whatever number value is seen.
Mandatory, May-encrypt, Protected, Vendor-bit = Not used in wireshark, but refers to the flags in the diameter message
#sf25us
Diameter profile
<avp name="Charging-Rule-Install" code="1001" mandatory="must" may-encrypt="yes" protected="may" vendor-bit="must" vendor-id="TGPP">
<grouped>
<gavp name="Charging-Rule-Definition"/>
<gavp name="Charging-Rule-Name"/>
<gavp name="Charging-Rule-Base-Name"/>
<gavp name="Bearer-Identifier"/>
<gavp name="Rule-Activation-Time"/>
<gavp name="Rule-Deactivation-Time"/>
<gavp name="Resource-Allocation-Notification"/>
</grouped>
</avp>
<avp name="Charging-Rule-Base-Name" code="1004" mandatory="must" may-encrypt="yes" protected="may" vendor-bit="must" vendor-id="TGPP">
<type type-name="UTF8String"/>
</avp>
#sf25us
GTP
MME
S-GW
P-GW
HSS
PCRF
S1-U
S11
S6a
S5/S8
Gx
SGi
Internet
S1-MME
GTP-C
GTP-C
GTP-U
GTP-U
#sf25us
GTPv2
#sf25us
GTPv1
#sf25us
GTP
#sf25us
GTP Tunnel Viewing Version1
Using the previous example, the 1st thing I want to do is look at my GTP endpoints and not just the upper layer IP
#sf25us
GTP Tunnel Viewing Version2
Using the previous example, the 1st thing I want to do is look at my GTP endpoints and not just the upper layer IP
(both addresses will be resolved using the _host field)
#sf25us
GTP
2nd, we need to de’dup the packets so that we can use the TCP analysis feature in wireshark (assuming we’re looking for things like packet loss).
Putting a display filter does not help in the cases where you capture S1-u, S5, and Sgi in the same capture. Wireshark analysis feature is based on all packets in the capture regardless of whether you are view them or not.
AFTER
BEFORE
Original File
#sf25us
Let’s build a profile
#sf25us
Sharing tunneled payload
#sf25us
Editcap to remove true
.\editcap.exe -w –I .0001 C:\temp\roamingIssue.pcap C:\temp\roamDedup.pcap
153 packets seen, 12 packets skipped with duplicate time window equal to or less than 0.000100000 seconds.
duplicate packets. Most useful on S5 interface that leaves SGW, and arrives at PGW.
For large files use capinfos to determine size, and calculate the size of the chunks that the pcap can be broken up into.
Mergecap…..
Tools, Tools, Tools, they are there for you.
#sf25us
Slicing packets in large traces file to reduce the file size
editcap -s 128 superbowl.pcap superbowl_128.pcapng
Take a larger trace and slice the packets after byte 128 and save to a new file. This means I won’t get the complete payload, but that is ok in the cases where your troubleshooting packet loss using TCP analysis. You can always go back to the original trace file if you need the payload. This will dramatically reduce the size of a trace.
Split a large file into many smaller ones.
editcap -c <number of packets per file> superbowl.pcap smallSuperbowl.pcap
Becomes smallSuperbowl_0000.pacp _0001.pcap,
smallSuperbowl_0000.pacp _0002.pcap,
etc.
#sf25us
Recommend doing this before deduping packets.
tshark.exe -r 4perToBeDeduped.pcap -T fields -e tcp.seq_raw | sort | uniq -c
4 1000461189
4 1218696134
4 1218697032
4 1218698412
4 1218699792
4 1218701172
1 2689484057
1 2689485425
1 2689486793
1 2689488161
4 2710474120
4 2710476617
4 2746774803
-T <def> = defines output type (fields, text, ps). When using –e this MUST be <fields>
-e <field> = which field to print. Must be repeated print multiple fields
Verify each packet has hit every tap
#sf25us
Use a common host file, and distribute to all my profiles using a batch file
#sf25us
IMS / Volte
Volte is really nothing more that SIP/RTP (VoIP) over LTE with a guaranteed bearer and more favorable DSCP markers on the packet.
Most tools for SIP will work with trouble shooting Volte
#sf25us
A quick view into the nature, and timing of RTP packets.
Knowing your RTP timers help when using these tools.
Example; I know in this case active conversation packets are 20ms, and silent packets are 160ms
RTP Stream Analysis
#sf25us
IO Graph for jitter/packet arrival
1
4
2
3
#sf25us
Note the left and right sides of the || operator
#sf25us
CTRL-V or
Left side of conversation filter
To get display filter expression
#sf25us
Left graph depicts what we expect to see. That is 20ms time on active packets, and 160ms on silent packets. Using this graph you can tell who was speaking during this conversation.
Right graph is showing the Caller with an active call time at 40ms instead of 20ms, with spikes down to 0ms. Hmm.
#sf25us
Set time format to “seconds since previous displayed packet”
Adjust display filter to just show caller.
We can now see that the caller sends 2 packets every 40ms. That is one at the 40ms mark, then another one within the same sub-millisecond.
??
This was found to be uplink packet time scheduling on the eNodeB. Packets could only be requested on the 40ms timeslot. So UE would send 2 at a time.
#sf25us
4G to 5G
What was combined functions on 4G have now been separated, and segregated into their own network functions
Functions are now treated like services, and can be called upon with API like queries.
#sf25us
NETWORK FUNCTIONS
#sf25us
User plane protocols unchanged
The highlighted part still uses GTP, like the previous 4G slides
#sf25us
RAN Signaling
S1AP has changed to NGAP, but is the same type of informational elements, just more
Also is still on top of SCTP
#sf25us
Core signaling over http2
#sf25us
HTTP2 information
for each stream ID
#sf25us
JSON over HTTP2
Current WS versions require you to filter like this
This is not great, because it doesn’t match a key value pair.
So any key with a value of “ACTIVATING” could be
matched, as long as the key upCnxState exist.
Development version allows for absolute filters
While this allows for more exact filtering, there is still work to be done.
#sf25us
LUA dissector pt1
#sf25us
LUA dissector pt2
-- Append "NAT64" address to the Info column with a post-dissector, and create it's own tree.
�-- create a new protocol so we can register a post-dissector
local myproto = Proto("nat64","NAT 64 encapsulated IPv4")
-- create a new field so we can assign that to the Proto
myproto_source = ProtoField.string("nat64.ipv4", "IPv4 Address")
-- Assign field to the proto object.
myproto.fields = { myproto_source }
-- register our new dummy protocol for post-dissection
register_postdissector(myproto)
�--local nat64_field = Field
�-- the dissector function callback
function myproto.dissector(tvb,pinfo,tree)
--
end
#sf25us
LUA dissector pt3
function myproto.dissector(tvb,pinfo,tree)
local dest = tostring(pinfo.dst)
local source = tostring(pinfo.src)
if string.find(dest, "7700") then
first, second = string.match(dest, "%d+:%d+:%d+:%d+:%d+:%d+:(.*):(.*)$")
firstOct, secondOct = string.match(first, "(..)(.+)")
thirdOct, fourthOct = string.match(second, "(..)(.+)")
ipv4Address = tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16)
pinfo.cols.info:append( " NAT64 " .. tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16))
local subtree = tree:add(myproto,tvb(), "NAT64 v4")
subtree:add(myproto_source, ipv4Address)
--repeat for source
else
end
#sf25us
-- Append "NAT64" address to the Info column with a post-dissector, and create it's own tree.
�-- create a new protocol so we can register a post-dissector
local myproto = Proto("nat64Test","NAT1 64 encapsulated IPv4")
myproto_source = ProtoField.string("nat64.ipv4", "IPv4 Address")
myproto.fields = { myproto_source }
�-- the dissector function callback
function myproto.dissector(tvb,pinfo,tree)
local dest = tostring(pinfo.dst)
local source = tostring(pinfo.src)
if string.find(dest, "7700") then
first, second = string.match(dest, "%d+:%d+:%d+:%d+:%d+:%d+:(.*):(.*)$")
firstOct, secondOct = string.match(first, "(..)(.+)")
thirdOct, fourthOct = string.match(second, "(..)(.+)")
ipv4Address = tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16)
pinfo.cols.info:append( " NAT64 " .. tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16))
local subtree = tree:add(myproto,tvb(), "NAT64 v4")
subtree:add(myproto_source, ipv4Address)
elseif string.find(source, "7700") then
first, second = string.match(source, "%d+:%d+:%d+:%d+:%d+:%d+:(.*):(.*)$")
firstOct, secondOct = string.match(first, "(..)(.+)")
thirdOct, fourthOct = string.match(second, "(..)(.+)")
ipv4Address = tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16)
pinfo.cols.info:append( " NAT64 " .. tonumber(firstOct, 16) .. "." .. tonumber(secondOct,16) .. "." .. tonumber(thirdOct,16) .. "." .. tonumber(fourthOct,16))
local subtree = tree:add(myproto,tvb(), "NAT64 v4")
subtree:add(myproto_source, ipv4Address)
else
end
end
�-- register our new dummy protocol for post-dissection
register_postdissector(myproto)
#sf25us
#sf25us
https://conference.wireshark.org/sharkfest-25-us-2024/talk/WDQNXL/feedback/
#sf25us