Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 60 additions & 22 deletions containers/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -1093,31 +1093,16 @@ func (c *Container) enrichDestinationKey(key common.DestinationKey) common.Desti
return key
}

// migrateConnectionKeyIfNeeded updates conn.DestinationKey from IP to FQDN when
// DNS becomes available, and migrates the corresponding connectionStats entry.
// This fixes the root cause of duplicate metrics: connections opened before DNS
// was cached get an IP-based key, while later connections get an FQDN-based key.
// By migrating the key in-place, both converge to the same key.
// migrateConnectionKeyToFQDN updates conn.DestinationKey from IP to the given FQDN
// and migrates the corresponding connectionStats entry.
// Must be called under c.lock.
func (c *Container) migrateConnectionKeyIfNeeded(conn *ActiveConnection) {
if conn == nil {
return
}
if !isIPAddress(conn.DestinationKey.GetDestinationWorkload().Name) {
return
}
ip := conn.DestinationKey.ActualDestinationIfKnown().IP()
if !common.IsIpExternal(ip) {
return
}
domain := c.registry.getDomain(ip)
if domain == nil {
func (c *Container) migrateConnectionKeyToFQDN(conn *ActiveConnection, fqdn string) {
oldKey := conn.DestinationKey
newKey := oldKey.WithResolvedDomain(fqdn)
if oldKey == newKey {
return
}

oldKey := conn.DestinationKey
newKey := oldKey.WithResolvedDomain(domain.FQDN)

// Migrate connectionStats from old key to new key
oldStats, _ := c.connectionStats.Get(oldKey)
if oldStats != nil {
Expand All @@ -1137,6 +1122,27 @@ func (c *Container) migrateConnectionKeyIfNeeded(conn *ActiveConnection) {
conn.DestinationKey = newKey
}
Comment thread
mayankpande88 marked this conversation as resolved.

// migrateConnectionKeyIfNeeded updates conn.DestinationKey from IP to FQDN when
// DNS becomes available, and migrates the corresponding connectionStats entry.
// Must be called under c.lock.
func (c *Container) migrateConnectionKeyIfNeeded(conn *ActiveConnection) {
if conn == nil {
return
}
if !isIPAddress(conn.DestinationKey.GetDestinationWorkload().Name) {
return
}
ip := conn.DestinationKey.ActualDestinationIfKnown().IP()
if !common.IsIpExternal(ip) {
return
}
domain := c.registry.getDomain(ip)
if domain == nil {
return
}
c.migrateConnectionKeyToFQDN(conn, domain.FQDN)
}

// onL7RequestWithResult processes an L7 request and returns the result along with whether it should be retried
// socketInfo contains connection tuple extracted directly from fd in eBPF (nil if extraction failed)
func (c *Container) onL7RequestWithResult(pid uint32, fd uint64, timestamp uint64, r *l7.RequestData, socketInfo *ebpftracer.SocketInfo) (map[netaddr.IP]*common.Domain, L7RequestResult) {
Expand Down Expand Up @@ -1238,7 +1244,20 @@ func (c *Container) onL7RequestWithResult(pid uint32, fd uint64, timestamp uint6
// Use new HTTP processor - parse once, use everywhere
httpCtx := NewHTTPRequestContext(r, conn, dnsResolver)

// Update stats with extracted trace ID
// If DestinationKey still has a raw IP but the HTTP Host header has an FQDN,
// use it to update the connection key and populate ip_to_fqdn.
var ip2fqdn map[netaddr.IP]*common.Domain
if isIPAddress(conn.DestinationKey.GetDestinationWorkload().Name) &&
httpCtx.Host != "" && !isIPAddress(httpCtx.Host) {
ip := conn.DestinationKey.ActualDestinationIfKnown().IP()
if common.IsIpExternal(ip) {
c.migrateConnectionKeyToFQDN(conn, httpCtx.Host)
domain := common.NewDomain(httpCtx.Host, []netaddr.IP{ip})
ip2fqdn = map[netaddr.IP]*common.Domain{ip: domain}
}
}

// Update stats with extracted trace ID (uses resolved key if migrated above)
c.l7Stats.observe(r.Protocol, r.Status.Http(), httpCtx.Method, httpCtx.Path, r.Duration, conn.DestinationKey, conn.srcWorkload, r, httpCtx.TraceID)

// LLM tracking with improved detection
Expand All @@ -1252,6 +1271,9 @@ func (c *Container) onL7RequestWithResult(pid uint32, fd uint64, timestamp uint6
trace.HttpRequest(httpCtx.Method, httpCtx.Path, r.Status, r.Duration, r.PayloadSize,
httpCtx.PayloadBase64, httpCtx.Headers, httpCtx.ResponseBase64, httpCtx.Host)
}
if ip2fqdn != nil {
return ip2fqdn, L7RequestProcessed
}
case l7.ProtocolHTTP2:
// HTTP/2 stats will be updated in the loop below
// Each HTTP/2 connection has its own HPACK dynamic table, so we use per-fd
Expand All @@ -1273,6 +1295,22 @@ func (c *Container) onL7RequestWithResult(pid uint32, fd uint64, timestamp uint6
pid, fd, len(requests), activeCount)
}

// If DestinationKey still has a raw IP, try to resolve from :authority header
if isIPAddress(conn.DestinationKey.GetDestinationWorkload().Name) {
for _, req := range requests {
authority := stripPort(req.Authority)
if authority != "" && !isIPAddress(authority) {
ip := conn.DestinationKey.ActualDestinationIfKnown().IP()
if common.IsIpExternal(ip) {
c.migrateConnectionKeyToFQDN(conn, authority)
domain := common.NewDomain(authority, []netaddr.IP{ip})
return map[netaddr.IP]*common.Domain{ip: domain}, L7RequestProcessed
}
Comment thread
mayankpande88 marked this conversation as resolved.
break
}
}
}

// Feed active streams to LLM stream tracker for SSE-based completion detection
// This handles streaming LLM responses that don't send END_STREAM until complete
if c.llmStreamTracker != nil {
Expand Down
20 changes: 10 additions & 10 deletions ebpftracer/ebpf.go

Large diffs are not rendered by default.

65 changes: 57 additions & 8 deletions ebpftracer/ebpf/tcp/state.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,32 +215,81 @@ struct trace_event_raw_args_with_fd__stub {
__u64 fd;
};

// sys_enter_connect args: fd, uservaddr, addrlen
struct trace_event_raw_sys_enter_connect__stub {
__u64 unused;
__u64 unused2;
__u64 fd;
void *uservaddr;
__u64 addrlen;
};

// Stores fd and destination port extracted from connect() sockaddr
struct connect_args {
__u64 fd;
__u16 dport;
};

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(key_size, sizeof(__u64));
__uint(value_size, sizeof(struct connect_args));
__uint(max_entries, 10240);
} connect_args_by_pid_tgid SEC(".maps");

SEC("tracepoint/syscalls/sys_enter_connect")
int sys_enter_connect(void *ctx) {
struct trace_event_raw_args_with_fd__stub args = {};
struct trace_event_raw_sys_enter_connect__stub args = {};
if (bpf_probe_read(&args, sizeof(args), ctx) < 0) {
return 0;
}
__u64 id = bpf_get_current_pid_tgid();

struct connect_args ca = {};
ca.fd = args.fd;

// Extract destination port from sockaddr
if (args.uservaddr && args.addrlen >= 4) {
__u16 family = 0;
bpf_probe_read_user(&family, sizeof(family), args.uservaddr);
if (family == 2 && args.addrlen >= 8) { // AF_INET
__be16 port_be = 0;
bpf_probe_read_user(&port_be, sizeof(port_be), args.uservaddr + 2);
ca.dport = bpf_ntohs(port_be);
} else if (family == 10 && args.addrlen >= 8) { // AF_INET6
__be16 port_be = 0;
bpf_probe_read_user(&port_be, sizeof(port_be), args.uservaddr + 2);
ca.dport = bpf_ntohs(port_be);
}
}

bpf_map_update_elem(&connect_args_by_pid_tgid, &id, &ca, BPF_ANY);
// Keep fd_by_pid_tgid for backwards compatibility with inet_sock_set_state
bpf_map_update_elem(&fd_by_pid_tgid, &id, &args.fd, BPF_ANY);
return 0;
}

SEC("tracepoint/syscalls/sys_exit_connect")
int sys_exit_connect(struct trace_event_raw_sys_exit__stub* ctx) {
__u64 id = bpf_get_current_pid_tgid();
__u64 *fdp = bpf_map_lookup_elem(&fd_by_pid_tgid, &id);
if (!fdp) {
struct connect_args *ca = bpf_map_lookup_elem(&connect_args_by_pid_tgid, &id);
if (!ca) {
// Fallback to fd_by_pid_tgid for inet_sock_set_state path
bpf_map_delete_elem(&fd_by_pid_tgid, &id);
return 0;
}
struct connection_id cid = {};
cid.pid = id >> 32;
cid.fd = *fdp;
cid.fd = ca->fd;
__u16 dport = ca->dport;
bpf_map_delete_elem(&connect_args_by_pid_tgid, &id);

struct connection *conn = bpf_map_lookup_elem(&active_connections, &cid);
if (!conn && ctx->ret == 0) { // non-TCP connection
struct connection conn = {};
conn.timestamp = bpf_ktime_get_ns();
bpf_map_update_elem(&active_connections, &cid, &conn, BPF_ANY);
if (!conn && ctx->ret == 0) { // non-TCP connection (e.g., UDP)
struct connection new_conn = {};
new_conn.timestamp = bpf_ktime_get_ns();
new_conn.dport = dport;
bpf_map_update_elem(&active_connections, &cid, &new_conn, BPF_ANY);
}

struct l7_request_key k = {
Expand Down
Loading