sergey-safarov left a comment (kamailio/kamailio#2092)

Here is `Cursor` analyse of the issue.

# Siptrace outbound TCP/TLS source port fix

## 1. Root cause

**Why the wrong port is reported**

- For **outbound** TCP/TLS, the OS assigns an **ephemeral local port** when the 
connection is created (`connect()`). The real source address is only known 
after the socket is connected (and can be read with `getsockname()`).
- Siptrace (and any subscriber to `SREV_NET_DATA_SENT`) receives a 
**dest_info_t** whose **send_sock** points to a **socket_info_t**.
- **socket_info_t** describes a **listening** socket (e.g. `tcp:1.2.3.4:5060`). 
It is the “hint” used to choose the outbound interface, not the actual socket 
used for the connection.
- So siptrace uses `dst->send_sock->sock_str` (or `useinfo.sock_str`) and 
therefore always logs the **listening** address/port, not the **connected** 
socket’s local address/port.

**Where the real address actually is**

- In [src/core/tcp_main.c](src/core/tcp_main.c), `tcp_do_connect()` already 
gets the real local address with `getsockname()` and stores it in 
`res_local_addr` (lines 1456–1461, 1475, 1504).
- That value is written into the **tcp_connection** as `c->rcv.dst_ip` and 
`c->rcv.dst_port` (and `c->rcv.proto`). For outbound connections, `rcv.dst_*` 
is the **local** endpoint (see [src/core/tcp_conn.h](src/core/tcp_conn.h) 
macros `TCP_RCV_LADDR`, `TCP_RCV_LPORT`).
- So the correct source address **exists** in the TCP layer 
(`tcp_connection->rcv`) but is **never passed** to the code that raises 
`SREV_NET_DATA_SENT`. The event is raised in 
[src/core/forward.h](src/core/forward.h) (lines 349–356) with `netinfo.dst = 
dst` only; `dst->send_sock` is still the listening socket.

```mermaid
flowchart LR
  subgraph core [Core]
    msg_send["msg_send_buffer()"]
    tcp_send["tcp_send(dst)"]
    conn["tcp_connection c"]
    event["SREV_NET_DATA_SENT"]
    msg_send --> tcp_send
    tcp_send --> conn
    tcp_send --> event
    event -->|"nd->dst (send_sock = listen)"| siptrace
  end
  subgraph siptrace [siptrace]
    siptrace["siptrace_net_data_sent()"]
  end
  conn -.->|"c->rcv has real local addr (not passed)"| conn
```



---

## 2. Relevant code paths

**Where “from” is set for outbound**


| Path                    | File / function                                     
                                                   | Current source of “from”   
                         |
| ----------------------- | 
------------------------------------------------------------------------------------------------------
 | --------------------------------------------------- |
| Low-level send event    | [siptrace.c](src/modules/siptrace/siptrace.c) 
`siptrace_net_data_sent()` (lines 2456–2475)             | 
`new_dst.send_sock->sock_str` or `useinfo.sock_str` |
| TM/request-out callback | [siptrace.c](src/modules/siptrace/siptrace.c) 
`sip_trace()` with `get_onsend_info()` (lines 1379–1388) | 
`snd_inf->send_sock->sock_str`                      |
| Same for HEP            | 
[siptrace_hep.c](src/modules/siptrace/siptrace_hep.c) (e.g. line 674)           
                       | `dst.send_sock->port_no`                            |


**Core flow**

- [forward.h](src/core/forward.h): `msg_send_buffer()` calls `tcp_send(dst, 
from, ...)` then `sr_event_exec(SREV_NET_DATA_SENT, &evp)` with `evp.data = 
&netinfo`, `netinfo.dst = dst`.
- [tcp_main.c](src/core/tcp_main.c): `tcp_send()` either finds an existing 
connection (`tcpconn_lookup` / `tcpconn_get`) or creates one (`tcpconn_connect` 
→ `tcp_do_connect`). In both cases it has a `struct tcp_connection *c` with 
`c->rcv.dst_ip`, `c->rcv.dst_port`, `c->rcv.proto` set to the **actual local** 
address for outbound.

---

## 3. Optional way to fix it

**Idea:** Have the core record the **actual local address** used for the send 
in **dest_info_t**, and have siptrace use it for TCP/TLS when present.

**A. Core changes**

1. **Extend dest_info_t** in [src/core/ip_addr.h](src/core/ip_addr.h) with an 
optional “sent local” address, so the event can carry the real source without 
holding a pointer to the connection (avoid lifetime issues). For example:
  - Add a small struct used only when the send is TCP/TLS and the connection is 
known, e.g. `struct { struct ip_addr ip; unsigned short port; char proto; int 
set; } sent_local;` (or a pointer to a copy, or a `receive_info_t *` with clear 
lifetime rules). The important part is to **copy** ip/port/proto from `c->rcv`, 
not to store a pointer to `c->rcv`.
2. **In tcp_send()** in [src/core/tcp_main.c](src/core/tcp_main.c): whenever a 
connection `c` is used to send (either found or newly created), **before** 
returning success, copy the local address from `c->rcv` into `dst->sent_local` 
(or the chosen field): `dst_ip` → ip, `dst_port` → port, `proto` → proto. Do 
this for both TCP and TLS (and any other stream that uses the same convention). 
Do **not** store a pointer to `c` or `c->rcv`; the connection may be shared or 
closed after return.
3. **No change to the event itself:** `SREV_NET_DATA_SENT` already receives 
`netinfo.dst`; once `dst` carries the optional “sent local” info, subscribers 
can use it.

**B. Siptrace (and HEP) changes**

1. **siptrace_net_data_sent()** in 
[src/modules/siptrace/siptrace.c](src/modules/siptrace/siptrace.c): when 
building `sto.fromip` for outbound:
  - If `nd->dst->sent_local.set` (or equivalent) is set and proto is TCP/TLS, 
format `sto.fromip` as `proto:ip:port` from `sent_local` (using e.g. 
`ip_addr2a(&nd->dst->sent_local.ip)` and the port/proto).
  - Otherwise keep current behavior: use `trace_local_ip` if set, else 
`send_sock->sock_str` / `useinfo.sock_str`.
2. **TM/onsend path** in [siptrace.c](src/modules/siptrace/siptrace.c) 
(`sip_trace()` with `get_onsend_info()`): this runs **before** the actual send, 
so at that moment there is no connection yet and no “sent local” address. So 
this path will continue to show the listening socket for TCP/TLS unless the 
flow is changed (e.g. callback after send with updated dest_info). The plan 
does not require changing this path; the main fix is for `SREV_NET_DATA_SENT`.
3. **HEP** in [siptrace_hep.c](src/modules/siptrace/siptrace_hep.c): wherever 
the source port is taken from `send_sock->port_no`, add a branch: if “sent 
local” is set for this dest, use its port (and ip/proto) for the HEP payload 
instead.

**C. Edge cases**

- **UDP:** No connection; leave `sent_local` unset and keep using `send_sock` 
as today.
- **SCTP:** Same as UDP for the typical case (listening socket); only extend 
“sent local” for TCP/TLS if that matches the current design.
- **Connection created in another process:** If in some builds the send is 
forwarded to another process and `tcp_send()` in the caller never sees the 
connection, that path would not set `sent_local`; siptrace would fall back to 
`send_sock` (current behavior). So behavior stays backward compatible.

---

## 4. Summary

- **Root cause:** Siptrace uses `dest_info_t.send_sock`, which is the 
**listening** socket. For outbound TCP/TLS the real source is the **connected** 
socket’s local address (ephemeral port), which lives only in 
`tcp_connection->rcv` and is not exposed to the event.
- **Fix:** Core copies the actual local address from the connection into 
`dest_info_t` (e.g. `sent_local`) when sending over TCP/TLS; siptrace (and HEP) 
use this when present and fall back to `send_sock` otherwise. No change to 
event semantics; only an optional extra field and siptrace/HEP logic to prefer 
it for TCP/TLS.



-- 
Reply to this email directly or view it on GitHub:
https://github.com/kamailio/kamailio/issues/2092#issuecomment-3952040548
You are receiving this because you are subscribed to this thread.

Message ID: <kamailio/kamailio/issues/2092/[email protected]>
_______________________________________________
Kamailio - Development Mailing List -- [email protected]
To unsubscribe send an email to [email protected]
Important: keep the mailing list in the recipients, do not reply only to the 
sender!

Reply via email to