Skip to content

Analyzing NF Discovery Requests: Step-by-Step Trace of NFSelect Queries

Note

Author: Che-Wei Lin
Date: 2026/1/21


Introduction

The Network Repository Function (NRF) serves as the service discovery backbone of the 5G Core network, enabling every network function to discover and select peers dynamically. In a Service-Based Architecture (SBA), NFs do not have static configurations for their peers; instead, they rely on the NRF to provide real-time status and endpoint information for available services.

In this article, we'll walk through underlying architecture of the Nnrf_NFDiscovery service, the specific query parameters required for NF selection, and practical methods for capturing SBI traffic using tcpdump.


1. NF Discovery Architecture

The Discovery Endpoint

In free5GC, the NRF exposes the Nnrf_NFDiscovery service at:

GET http://<nrf-ip>:<port>/nnrf-disc/v1/nf-instances

This endpoint is implemented in:

// File: internal/sbi/api_discovery.go

func (s *Server) getNfDiscoveryRoutes() []Route {
    return []Route{
        {
            "SearchNFInstances",
            http.MethodGet,
            "/nf-instances",
            s. HTTPSearchNFInstances,
        },
    }
}

func (s *Server) HTTPSearchNFInstances(c *gin.Context) {
    s. Processor().HandleNFDiscoveryRequest(c, c.Request.URL.Query())
}

Request Flow Overview

┌─────────┐      ┌─────────────┐      ┌──────────────┐      ┌─────────┐
│ NF (e.g.│ GET  │   NRF SBI   │      │  Discovery   │      │ MongoDB │
│   AMF)  │─────>│   Server    │─────>│  Processor   │─────>│  Query  │
└─────────┘      └─────────────┘      └──────────────┘      └─────────┘
    │                   │                      │                   │
    │                   │                      │                   │
    │                   │                      │<──────────────────┤
    │                   │                      │  NF Profiles      │
    │                   │<─────────────────────┤                   │
    │<──────────────────┤  200 OK + JSON       │                   │
    │  NF Profile List  │                      │                   │

The Core Processing Functions

The discovery request goes through these key functions:

  1. HandleNFDiscoveryRequest - HTTP handler (entry point)
  2. validateQueryParameters - Validates mandatory parameters
  3. NFDiscoveryProcedure - Main processing logic
  4. buildFilter - Constructs MongoDB filter from query params
  5. mongoapi.RestfulAPIGetMany - Queries the NfProfile collection

2. Query Parameters

free5GC NRF supports 32+ query parameters for fine-grained NF selection. Here are the most important ones:

Mandatory Parameters

Parameter Type Description Example
target-nf-type string Type of NF to discover SMF, UDM, PCF
requester-nf-type string Type of requesting NF AMF, SMF

Note: Both parameters are required. Missing either will result in HTTP 400 error with "Loss mandatory parameter".

Common Optional Parameters

Parameter Description Example Query MongoDB Filter Impact
target-plmn-list PLMN ID for roaming scenarios [{"mcc":"208","mnc":"93"}] Adds $elemMatch on plmnList
snssais Network slice selection [{"sst":1,"sd":"010203"}] Filters by sNssais array
dnn Data Network Name internet Checks SMF/UPF dnnSmfInfoList
preferred-locality Geographic preference area1 Filters by locality field

How Parameters Map to MongoDB Filters

Based on actual trace logs from our experiments, the buildFilter() function converts query parameters to BSON filters:

File: internal/sbi/processor/nf_discovery.go

Input Parameters:

target-nf-type=SMF
requester-nf-type=AMF
snssais=[{"sst":1,"sd":"010203"}]
dnn=internet

Generated MongoDB Filter:

map[$and:[
  map[nfType: SMF]
  map[$or:[
    map[allowedNfTypes:AMF]
    map[allowedNfTypes:map[$exists:false]]
  ]]
  map[$or:[
    map[sNssais: map[$elemMatch: map[sd:010203 sst:1]]]
    map[sNssais:map[$exists:false]]
  ]]
  map[smfInfo. sNssaiSmfInfoList: map[$elemMatch:map[dnnSmfInfoList:map[$elemMatch:map[dnn:internet]]]]]
]]


3. Capturing Discovery with tcpdump

Step 1: Setup Environment

Ensure your free5GC deployment is running:

# Start free5GC
cd ~/free5gc
./run. sh

# Verify NRF is running
curl http://127.0.0.10:8000/nnrf-nfm/v1/nf-instances

Step 2: Enable Trace Logging

Before running experiments, modify your NRF configuration:

# File: config/nrfcfg.yaml

logger:
  enable: true
  level: trace      # Change from 'info' to 'trace'
  reportCaller: true # Change from 'false' to 'true'

Then restart free5GC to apply changes:

# Stop free5GC
./force_kill.sh

# Restart
./run.sh

Step 3: Start tcpdump

Capture SBI traffic on the NRF interface:

# Capture on loopback (if running locally)
sudo tcpdump -i lo -w nrf-discovery.pcap \
  'tcp port 8000 and host 127.0.0.10'

# For Docker deployments, capture on bridge network
sudo tcpdump -i br-<network-id> -w nrf-discovery.pcap \
  'tcp port 8000'

Step 4: Trigger a Discovery Request

Simulate an AMF discovering an SMF:

curl -v -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
target-nf-type=SMF&\
requester-nf-type=AMF&\
snssais=[{\"sst\":1,\"sd\": \"010203\"}]&\
dnn=internet" | jq .

Step 5: Analyze the Capture

# Convert to text for analysis
tcpdump -r nrf-discovery.pcap -A | less

# Or use tshark for better HTTP/2 analysis
tshark -r nrf-discovery.pcap -Y "http2" -T fields \
  -e http2.header.name -e http2.header.value

Actual Packet Capture Results

Pcap File Statistics:

  • Total packets captured: 343
  • Capture file size: 53KB
  • Discovery requests captured: 12 (including retries)
  • Timestamp range: 12:28:30 - 13:57:32

TCP Three-Way Handshake

12:28:30.489378 IP 127.0.0.1.38436 > 127.0.0.10.8000: Flags [S], seq 766027190, win 65495
12:28:30.489408 IP 127.0.0.10.8000 > 127.0.0.1.38436: Flags [S.], seq 2423474686, ack 766027191
12:28:30.489418 IP 127.0.0.1.38436 > 127.0.0.10.8000: Flags [.], ack 1, win 512

Analysis: Connection established in 30 microseconds (489378 → 489408 → 489418)

HTTP Request (SMF Discovery)

12:28:30.489529 IP 127.0.0.1.38436 > 127.0.0.10.8000: Flags [P.], seq 1:291, length 290

GET /nnrf-disc/v1/nf-instances?target-nf-type=SMF&requester-nf-type=AMF&snssais=%5B%7B%22sst%22%3A1%2C%22sd%22%3A%22010203%22%7D%5D&dnn=internet&plmn-id=%7B%22mcc%22%3A%22208%22%2C%22mnc%22%3A%2293%22%7D HTTP/1.1
Host: 127.0.0.10:8000
User-Agent: curl/8.12.1
Accept: application/json

Packet Details:

  • Source: 127.0.0.1:38436 (Client)
  • Destination: 127.0.0.10:8000 (NRF)
  • HTTP Method: GET
  • Payload length: 290 bytes
  • URL-encoded query parameters (note the %5B, %7B encoding)

HTTP Response (200 OK)

12:28:30.493105 IP 127.0.0.10.8000 > 127.0.0.1.38436: Flags [P.], seq 1:1792, length 1791

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Mon, 05 Jan 2026 12:28:30 GMT
Content-Length: 1666

{"validityPeriod":100,"nfInstances":[{"nfInstanceId":"343aee23-749e-4835-a8b9-fb9174a162ca","nfType":"SMF","nfStatus":"REGISTERED","plmnList":[{"mcc":"208","mnc":"93"}],"sNssais":[{"sst":1,"sd":"010203"},{"sst":1,"sd":"112233"}],"ipv4Addresses":["127.0.0.2"],"locality":"area1","smfInfo":{"sNssaiSmfInfoList":[{"sNssai":{"sst":1,"sd":"010203"},"dnnSmfInfoList":[{"dnn":"internet"}]},{"sNssai":{"sst":1,"sd":"112233"},"dnnSmfInfoList":[{"dnn":"internet"}]}]},"customInfo":{"oauth2":false},"nfServices":[...]}]}

Response Details:

  • HTTP Status: 200 OK
  • Content-Type: application/json; charset=utf-8
  • Content-Length: 1666 bytes
  • Total packet length: 1791 bytes (includes HTTP headers)

Complete Transaction Timeline

Time (μs) Event Packet Size Direction
489378 SYN 60 bytes Client → NRF
489408 SYN-ACK 60 bytes NRF → Client
489418 ACK 52 bytes Client → NRF
489529 GET Request 290 bytes Client → NRF
489533 ACK 52 bytes NRF → Client
493105 HTTP 200 Response 1791 bytes NRF → Client
493114 ACK 52 bytes Client → NRF
493211 FIN 52 bytes Client → NRF
493239 FIN-ACK 52 bytes NRF → Client
493253 ACK 52 bytes Client → NRF

Total Transaction Time: 3.875 milliseconds (from initial SYN to final ACK)

  • Connection setup: 40μs
  • Request sent: 111μs after connection
  • Response received: 3.576ms after request
  • Connection teardown: 148μs

Other Discovery Requests in Capture

From analyzing the full pcap file, we captured these distinct discovery requests:

1. GET /nnrf-disc/v1/nf-instances?target-nf-type=SMF&... → HTTP/1.1 200 OK
2. GET /nnrf-disc/v1/nf-instances?target-nf-type=SMF&target-plmn-list=... → HTTP/1.1 200 OK
3. GET /nnrf-disc/v1/nf-instances?target-nf-type=UDM&... → HTTP/1.1 200 OK
4. GET /nnrf-disc/v1/nf-instances?target-nf-type=AUSF&... → HTTP/1.1 200 OK
5. GET /nnrf-disc/v1/nf-instances?requester-nf-type=AMF → HTTP/1.1 400 Bad Request

Key Observation: Request #5 returned HTTP 400 because target-nf-type parameter was missing, demonstrating NRF's parameter validation.

Hex Dump Analysis (Sample)

For deeper packet inspection, here's a hex dump of the HTTP GET request:

0x0030:  9748 10e0 4754 5420 2f6e 6e72 662d 6469  .H..GET./nnrf-di
0x0040:  7363 2f76 312f 6e66 2d69 6e73 7461 6e63  sc/v1/nf-instanc
0x0050:  6573 3f74 6172 6765 742d 6e66 2d74 7970  es?target-nf-typ
0x0060:  653d 534d 4626 7265 7175 6573 7465 722d  e=SMF&requester-
0x0070:  6e66 2d74 7970 653d 414d 4626 736e 7373  nf-type=AMF&snss
0x0080:  6169 733d 2535 4225 3742 2532 3273 7374  ais=%5B%7B%22sst

This shows the raw bytes of the HTTP request, with ASCII representation on the right side.


4. Correlating with free5GC NRF Logs

Enable Trace-Level Logging

As shown in Step 2 above, edit your NRF configuration:

# File: config/nrfcfg.yaml

logger:
  enable: true
  level: trace  # Critical for seeing MongoDB queries
  reportCaller:  true # Shows file paths and line numbers

Key Log Messages to Look For

1. Request Received

2026-01-05T10:30:15.456 [INFO][NRF][Disc] Handle NFDiscoveryRequest

Code Location:

File: /home/ubuntu/free5gc/NFs/nrf/internal/sbi/processor/nf_discovery.go
Function: (*Processor).HandleNFDiscoveryRequest

2. Query Filter Construction

2026-01-05T10:30:15.458 [TRACE][NRF][Disc] Query filter: map[$and:[map[nfType: SMF] map[$or:[map[allowedNfTypes:AMF] map[allowedNfTypes:map[$exists:false]]]] map[$or:[map[sNssais:map[$elemMatch:map[sd:010203 sst: 1]]] map[sNssais: map[$exists:false]]]] map[smfInfo.sNssaiSmfInfoList: map[$elemMatch:map[dnnSmfInfoList:map[$elemMatch:map[dnn: internet]]]]]]]

Code Location:

File: /home/ubuntu/free5gc/NFs/nrf/internal/sbi/processor/nf_discovery. go:131
Function: (*Processor).NFDiscoveryProcedure

This log line shows the exact MongoDB filter being executed.

3. HTTP Response Logging

2026-01-05T10:30:15.462 [INFO][NRF][GIN] | 200 |    127.0.0.18 | GET      /nnrf-disc/v1/nf-instances? target-nf-type=SMF&... 

Code Location:

File: /home/ubuntu/free5gc/util/logger/logger.go
Function: NewGinWithLogrus.ginToLogrus. func1

Shows the complete HTTP request path, response code, and client IP.

4. Validation Errors

If parameters are invalid:

[WARN][NRF][Disc] Invalid Parameter: Loss mandatory parameter

Returned to client:

{
  "title": "Invalid Parameter",
  "status": 400,
  "cause": "Loss mandatory parameter"
}

This follows RFC 7807 Problem Details format.

Complete Log Analysis Example

Here's a complete trace for an AMF→SMF discovery with all parameters:

2026-01-05T14:23:01.789 [INFO][NRF][GIN] | 200 |    127.0.0.18 | GET      /nnrf-disc/v1/nf-instances?target-nf-type=SMF&requester-nf-type=AMF&snssais=[{"sst":1,"sd":"010203"}]&dnn=internet&target-plmn-list=[{"mcc":"208","mnc":"93"}]

2026-01-05T14:23:01.789 [INFO][NRF][Disc][/home/ubuntu/free5gc/NFs/nrf/internal/sbi/processor/nf_discovery.go:26] Handle NFDiscoveryRequest

2026-01-05T14:23:01.789 [TRACE][NRF][Disc][/home/ubuntu/free5gc/NFs/nrf/internal/sbi/processor/nf_discovery.go:131] Query filter: map[$and:[map[nfType:SMF] map[$or:[map[allowedNfTypes:AMF] map[allowedNfTypes:map[$exists:false]]]] map[$or:[map[plmnList:map[$elemMatch:map[mcc:208 mnc:93]]]]] map[$or:[map[sNssais:map[$elemMatch:map[sd:010203 sst: 1]]] map[sNssais:map[$exists:false]]]] map[smfInfo.sNssaiSmfInfoList: map[$elemMatch:map[dnnSmfInfoList:map[$elemMatch:map[dnn: internet]]]]]]]

Analysis of this trace:

Timestamp Component Message Insight
14:23:01.789 GIN HTTP request logged Client IP: 127.0.0.18 (AMF), GET request with all params
14:23:01.789 Disc HandleNFDiscoveryRequest Entry point at line 26
14:23:01.789 Disc Query filter MongoDB filter with 5 conditions: type, authorization, PLMN, slice, DNN

Total processing time: < 1ms (all logs have same timestamp to millisecond precision)


5. Complete Example - AMF Discovering SMF for PDU Session

Let's trace a realistic scenario: An AMF needs to select an SMF to handle a PDU session establishment for a subscriber.

Scenario Details

  • Requested Slice: SST=1, SD=010203
  • DNN: internet
  • PLMN: MCC=208, MNC=93
  • Registered SMF: 343aee23-749e-4835-a8b9-fb9174a162ca at 127.0.0.2

Test Case 1: Basic SMF Discovery

Request:

curl -v -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
target-nf-type=SMF&\
requester-nf-type=AMF&\
snssais=[{\"sst\":1,\"sd\": \"010203\"}]&\
dnn=internet" | jq .

NRF Log Output:

[INFO][NRF][Disc] Handle NFDiscoveryRequest
[TRACE][NRF][Disc] Query filter: map[$and:[map[nfType: SMF] map[$or:[map[allowedNfTypes:AMF] map[allowedNfTypes:map[$exists:false]]]] map[$or:[map[sNssais:map[$elemMatch:map[sd:010203 sst:1]]] map[sNssais:map[$exists:false]]]] map[smfInfo.sNssaiSmfInfoList:map[$elemMatch: map[dnnSmfInfoList:map[$elemMatch:map[dnn:internet]]]]]]]

Response:

{
  "validityPeriod": 100,
  "nfInstances": [
    {
      "nfInstanceId": "343aee23-749e-4835-a8b9-fb9174a162ca",
      "nfType": "SMF",
      "nfStatus": "REGISTERED",
      "locality": "area1",
      "sNssais": [
        {"sst":  1, "sd": "010203"},
        {"sst":  1, "sd": "112233"}
      ]
    }
  ]
}

Analysis:

  • Discovered 1 SMF instance
  • SMF supports multiple slices (010203 and 112233)
  • Locality field present: "area1" (can be used for preferred-locality filtering)
  • Validity period: 100 seconds (cache TTL)

Test Case 2: Discovery with PLMN Filter

Request:

curl -v -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
target-nf-type=SMF&\
requester-nf-type=AMF&\
snssais=[{\"sst\":1,\"sd\":\"010203\"}]&\
dnn=internet&\
target-plmn-list=[{\"mcc\":\"208\",\"mnc\":\"93\"}]" | jq .

NRF MongoDB Filter:

map[$and:[
  map[nfType:SMF]
  map[$or:[map[allowedNfTypes:AMF] map[allowedNfTypes: map[$exists:false]]]]
  map[$or:[map[plmnList:map[$elemMatch: map[mcc:208 mnc:93]]]]]  // ← Added PLMN filter
  map[$or:[map[sNssais:map[$elemMatch:map[sd:010203 sst: 1]]] map[sNssais:map[$exists:false]]]]
  map[smfInfo.sNssaiSmfInfoList:map[$elemMatch:map[dnnSmfInfoList:map[$elemMatch:map[dnn:internet]]]]]
]]

Response:

{
  "validityPeriod": 100,
  "nfInstances": [
    {
      "nfInstanceId": "343aee23-749e-4835-a8b9-fb9174a162ca",
      "nfType": "SMF",
      "ipv4Addresses": ["127.0.0.2"]  // ← IP address included
    }
  ]
}

Analysis:

  • Same SMF discovered (PLMN matches)
  • Response now includes ipv4Addresses array
  • Additional filter condition added (PLMN list matching)

Test Case 3: Discovering Other NF Types

UDM Discovery:

curl -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
target-nf-type=UDM&\
requester-nf-type=AMF" | jq .

MongoDB Filter:

map[$and:[
  map[nfType:UDM]
  map[$or:[map[allowedNfTypes:AMF] map[allowedNfTypes: map[$exists:false]]]]
]]

Response:

{
  "validityPeriod": 100,
  "nfInstances": [
    {
      "nfInstanceId": "ed470d29-524f-4beb-9c15-dfa35cb68924",
      "nfType":  "UDM",
      "nfStatus": "REGISTERED"
    }
  ]
}

AUSF Discovery:

curl -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
target-nf-type=AUSF&\
requester-nf-type=AMF" | jq .

Response:

{
  "validityPeriod": 100,
  "nfInstances": [
    {
      "nfInstanceId": "23585e14-1f66-4276-8c6e-8ead28de840c",
      "nfType":  "AUSF",
      "nfStatus": "REGISTERED",
      "ipv4Addresses": ["127.0.0.9"]
    }
  ]
}

Analysis:

  • UDM and AUSF discoveries are simpler (no slice/DNN filtering needed)
  • Only type and authorization filters applied
  • Both returned successfully with NF instance IDs and status

Test Case 4: Error Handling - Missing Mandatory Parameter

Request (intentionally missing target-nf-type):

curl -X GET "http://127.0.0.10:8000/nnrf-disc/v1/nf-instances?\
requester-nf-type=AMF" | jq .

Response:

{
  "title": "Invalid Parameter",
  "status": 400,
  "cause": "Loss mandatory parameter"
}

HTTP Status: 400 Bad Request
Analysis:

  • Validation happens before database query
  • Returns RFC 7807 Problem Details format
  • No MongoDB query executed

MongoDB Verification

You can manually verify the discovered NF profiles in MongoDB:

# Connect to MongoDB
mongosh mongodb://127.0.0.1:27017/free5gc

# Query SMF profiles
db.NfProfile.find({
  "nfType": "SMF",
  "nfStatus": "REGISTERED",
  "sNssais": { "$elemMatch": { "sst": 1, "sd":  "010203" } }
}).pretty()

Expected Output:

{
  "_id": ObjectId("..."),
  "nfInstanceId": "343aee23-749e-4835-a8b9-fb9174a162ca",
  "nfType": "SMF",
  "nfStatus": "REGISTERED",
  "heartBeatTimer": 60,
  "plmnList": [
    {"mcc": "208", "mnc": "93"}
  ],
  "sNssais": [
    {"sst": 1, "sd":  "010203"},
    {"sst": 1, "sd":  "112233"}
  ],
  "ipv4Addresses": ["127.0.0.2"],
  "locality": "area1",
  "smfInfo": {
    "sNssaiSmfInfoList":  [
      {
        "sNssai": {"sst": 1, "sd": "010203"},
        "dnnSmfInfoList": [
          {"dnn": "internet"}
        ]
      }
    ]
  }
}


Conclusion

In a 5G Service Based Architecture, NF Discovery serves as the critical control mechanism that transforms a collection of isolated microservices into a synchronized, high-performance network. By leveraging the Network Repository Function (NRF) as a centralized service registry, the core evolves beyond static, hard-coded peer-to-peer configurations into a programmable environment. This architecture ensures that service consumers can dynamically bind to the most optimal service producer instance available.

This analysis shows how free5GC turns high-level NF discovery requests into concrete MongoDB queries inside the NRF discovery pipeline. By enabling trace-level logging, you can clearly see the exact filter conditions used during NF selection, making the discovery process much more transparent.


Reference

About

Hello! I'm Che Wei, Lin. I’ve recently begun my journey into 5G technology and the free5GC community. I hope you found this blog post helpful, and please feel free to reach out for further discussion.

Connect with Me