| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2020 Facebook */ |
| #include "bpf_iter.h" |
| #include "bpf_tracing_net.h" |
| #include <bpf/bpf_helpers.h> |
| #include <bpf/bpf_endian.h> |
| |
| char _license[] SEC("license") = "GPL"; |
| |
| static int hlist_unhashed_lockless(const struct hlist_node *h) |
| { |
| return !(h->pprev); |
| } |
| |
| static int timer_pending(const struct timer_list * timer) |
| { |
| return !hlist_unhashed_lockless(&timer->entry); |
| } |
| |
| extern unsigned CONFIG_HZ __kconfig; |
| |
| #define USER_HZ 100 |
| #define NSEC_PER_SEC 1000000000ULL |
| static clock_t jiffies_to_clock_t(unsigned long x) |
| { |
| /* The implementation here tailored to a particular |
| * setting of USER_HZ. |
| */ |
| u64 tick_nsec = (NSEC_PER_SEC + CONFIG_HZ/2) / CONFIG_HZ; |
| u64 user_hz_nsec = NSEC_PER_SEC / USER_HZ; |
| |
| if ((tick_nsec % user_hz_nsec) == 0) { |
| if (CONFIG_HZ < USER_HZ) |
| return x * (USER_HZ / CONFIG_HZ); |
| else |
| return x / (CONFIG_HZ / USER_HZ); |
| } |
| return x * tick_nsec/user_hz_nsec; |
| } |
| |
| static clock_t jiffies_delta_to_clock_t(long delta) |
| { |
| if (delta <= 0) |
| return 0; |
| |
| return jiffies_to_clock_t(delta); |
| } |
| |
| static long sock_i_ino(const struct sock *sk) |
| { |
| const struct socket *sk_socket = sk->sk_socket; |
| const struct inode *inode; |
| unsigned long ino; |
| |
| if (!sk_socket) |
| return 0; |
| |
| inode = &container_of(sk_socket, struct socket_alloc, socket)->vfs_inode; |
| bpf_probe_read_kernel(&ino, sizeof(ino), &inode->i_ino); |
| return ino; |
| } |
| |
| static bool |
| inet_csk_in_pingpong_mode(const struct inet_connection_sock *icsk) |
| { |
| return icsk->icsk_ack.pingpong >= TCP_PINGPONG_THRESH; |
| } |
| |
| static bool tcp_in_initial_slowstart(const struct tcp_sock *tcp) |
| { |
| return tcp->snd_ssthresh >= TCP_INFINITE_SSTHRESH; |
| } |
| |
| static int dump_tcp6_sock(struct seq_file *seq, struct tcp6_sock *tp, |
| uid_t uid, __u32 seq_num) |
| { |
| const struct inet_connection_sock *icsk; |
| const struct fastopen_queue *fastopenq; |
| const struct in6_addr *dest, *src; |
| const struct inet_sock *inet; |
| unsigned long timer_expires; |
| const struct sock *sp; |
| __u16 destp, srcp; |
| int timer_active; |
| int rx_queue; |
| int state; |
| |
| icsk = &tp->tcp.inet_conn; |
| inet = &icsk->icsk_inet; |
| sp = &inet->sk; |
| fastopenq = &icsk->icsk_accept_queue.fastopenq; |
| |
| dest = &sp->sk_v6_daddr; |
| src = &sp->sk_v6_rcv_saddr; |
| destp = bpf_ntohs(inet->inet_dport); |
| srcp = bpf_ntohs(inet->inet_sport); |
| |
| if (icsk->icsk_pending == ICSK_TIME_RETRANS || |
| icsk->icsk_pending == ICSK_TIME_REO_TIMEOUT || |
| icsk->icsk_pending == ICSK_TIME_LOSS_PROBE) { |
| timer_active = 1; |
| timer_expires = icsk->icsk_timeout; |
| } else if (icsk->icsk_pending == ICSK_TIME_PROBE0) { |
| timer_active = 4; |
| timer_expires = icsk->icsk_timeout; |
| } else if (timer_pending(&sp->sk_timer)) { |
| timer_active = 2; |
| timer_expires = sp->sk_timer.expires; |
| } else { |
| timer_active = 0; |
| timer_expires = bpf_jiffies64(); |
| } |
| |
| state = sp->sk_state; |
| if (state == TCP_LISTEN) { |
| rx_queue = sp->sk_ack_backlog; |
| } else { |
| rx_queue = tp->tcp.rcv_nxt - tp->tcp.copied_seq; |
| if (rx_queue < 0) |
| rx_queue = 0; |
| } |
| |
| BPF_SEQ_PRINTF(seq, "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X ", |
| seq_num, |
| src->s6_addr32[0], src->s6_addr32[1], |
| src->s6_addr32[2], src->s6_addr32[3], srcp, |
| dest->s6_addr32[0], dest->s6_addr32[1], |
| dest->s6_addr32[2], dest->s6_addr32[3], destp); |
| BPF_SEQ_PRINTF(seq, "%02X %08X:%08X %02X:%08lX %08X %5u %8d %lu %d ", |
| state, |
| tp->tcp.write_seq - tp->tcp.snd_una, rx_queue, |
| timer_active, |
| jiffies_delta_to_clock_t(timer_expires - bpf_jiffies64()), |
| icsk->icsk_retransmits, uid, |
| icsk->icsk_probes_out, |
| sock_i_ino(sp), |
| sp->sk_refcnt.refs.counter); |
| BPF_SEQ_PRINTF(seq, "%pK %lu %lu %u %u %d\n", |
| tp, |
| jiffies_to_clock_t(icsk->icsk_rto), |
| jiffies_to_clock_t(icsk->icsk_ack.ato), |
| (icsk->icsk_ack.quick << 1) | inet_csk_in_pingpong_mode(icsk), |
| tp->tcp.snd_cwnd, |
| state == TCP_LISTEN ? fastopenq->max_qlen |
| : (tcp_in_initial_slowstart(&tp->tcp) ? -1 |
| : tp->tcp.snd_ssthresh) |
| ); |
| |
| return 0; |
| } |
| |
| static int dump_tw_sock(struct seq_file *seq, struct tcp_timewait_sock *ttw, |
| uid_t uid, __u32 seq_num) |
| { |
| struct inet_timewait_sock *tw = &ttw->tw_sk; |
| const struct in6_addr *dest, *src; |
| __u16 destp, srcp; |
| long delta; |
| |
| delta = tw->tw_timer.expires - bpf_jiffies64(); |
| dest = &tw->tw_v6_daddr; |
| src = &tw->tw_v6_rcv_saddr; |
| destp = bpf_ntohs(tw->tw_dport); |
| srcp = bpf_ntohs(tw->tw_sport); |
| |
| BPF_SEQ_PRINTF(seq, "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X ", |
| seq_num, |
| src->s6_addr32[0], src->s6_addr32[1], |
| src->s6_addr32[2], src->s6_addr32[3], srcp, |
| dest->s6_addr32[0], dest->s6_addr32[1], |
| dest->s6_addr32[2], dest->s6_addr32[3], destp); |
| |
| BPF_SEQ_PRINTF(seq, "%02X %08X:%08X %02X:%08lX %08X %5d %8d %d %d %pK\n", |
| tw->tw_substate, 0, 0, |
| 3, jiffies_delta_to_clock_t(delta), 0, 0, 0, 0, |
| tw->tw_refcnt.refs.counter, tw); |
| |
| return 0; |
| } |
| |
| static int dump_req_sock(struct seq_file *seq, struct tcp_request_sock *treq, |
| uid_t uid, __u32 seq_num) |
| { |
| struct inet_request_sock *irsk = &treq->req; |
| struct request_sock *req = &irsk->req; |
| struct in6_addr *src, *dest; |
| long ttd; |
| |
| ttd = req->rsk_timer.expires - bpf_jiffies64(); |
| src = &irsk->ir_v6_loc_addr; |
| dest = &irsk->ir_v6_rmt_addr; |
| |
| if (ttd < 0) |
| ttd = 0; |
| |
| BPF_SEQ_PRINTF(seq, "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X ", |
| seq_num, |
| src->s6_addr32[0], src->s6_addr32[1], |
| src->s6_addr32[2], src->s6_addr32[3], |
| irsk->ir_num, |
| dest->s6_addr32[0], dest->s6_addr32[1], |
| dest->s6_addr32[2], dest->s6_addr32[3], |
| bpf_ntohs(irsk->ir_rmt_port)); |
| BPF_SEQ_PRINTF(seq, "%02X %08X:%08X %02X:%08lX %08X %5d %8d %d %d %pK\n", |
| TCP_SYN_RECV, 0, 0, 1, jiffies_to_clock_t(ttd), |
| req->num_timeout, uid, 0, 0, 0, req); |
| |
| return 0; |
| } |
| |
| SEC("iter/tcp") |
| int dump_tcp6(struct bpf_iter__tcp *ctx) |
| { |
| struct sock_common *sk_common = ctx->sk_common; |
| struct seq_file *seq = ctx->meta->seq; |
| struct tcp_timewait_sock *tw; |
| struct tcp_request_sock *req; |
| struct tcp6_sock *tp; |
| uid_t uid = ctx->uid; |
| __u32 seq_num; |
| |
| if (sk_common == (void *)0) |
| return 0; |
| |
| seq_num = ctx->meta->seq_num; |
| if (seq_num == 0) |
| BPF_SEQ_PRINTF(seq, " sl " |
| "local_address " |
| "remote_address " |
| "st tx_queue rx_queue tr tm->when retrnsmt" |
| " uid timeout inode\n"); |
| |
| if (sk_common->skc_family != AF_INET6) |
| return 0; |
| |
| tp = bpf_skc_to_tcp6_sock(sk_common); |
| if (tp) |
| return dump_tcp6_sock(seq, tp, uid, seq_num); |
| |
| tw = bpf_skc_to_tcp_timewait_sock(sk_common); |
| if (tw) |
| return dump_tw_sock(seq, tw, uid, seq_num); |
| |
| req = bpf_skc_to_tcp_request_sock(sk_common); |
| if (req) |
| return dump_req_sock(seq, req, uid, seq_num); |
| |
| return 0; |
| } |