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!