/*
 *      Copyright (C) 2009 IBM Corp.
 *      Copyright (C) 2010-2018 Red Hat Inc.
 *
 *      This file is part of systemtap, and is free software.  You can
 *      redistribute it and/or modify it under the terms of the GNU General
 *      Public License (GPL); either version 2, or (at your option) any
 *      later version.
 *      
 *      Version 1.0     wilder@us.ibm.com     2009-07-06
 */

/* Global counter definitions for mib TCP_MIB. */

// See the BZ1546179 block comment in tapset/linux/networking.stp for
// an explanation of the try/catch statements around sk_buff structure
// accesses.

%{
#include <net/sock.h>
#include <linux/tcp.h>
#include <linux/skbuff.h>
#include <net/route.h>
%}

global ActiveOpens
global AttemptFails
global CurrEstab
global EstabResets
// global InErrs, this mib is not yet supported
global InSegs
global OutRsts
global OutSegs
global PassiveOpens
global RetransSegs

/**
 * sfunction tcpmib_get_state - Get a socket's state
 * @sk: pointer to a struct sock
 *
 * Returns the sk_state from a struct sock.
 */
function tcpmib_get_state:long (sk:long)
{
	return @cast(sk, "sock", "kernel")->__sk_common->skc_state;
}

/**
 * sfunction tcpmib_local_addr - Get the source address
 * @sk: pointer to a struct inet_sock
 *
 * Returns the saddr from a struct inet_sock in host order.
 */
function tcpmib_local_addr:long(sk:long)
{
	return ntohl(__ip_sock_saddr(sk))
}

/**
 * sfunction tcpmib_remote_addr - Get the remote address
 * @sk: pointer to a struct inet_sock
 *
 * Returns the daddr from a struct inet_sock in host order.
 */
function tcpmib_remote_addr:long(sk:long)
{
	return ntohl(__ip_sock_daddr(sk))
}

/**
 * sfunction tcpmib_local_port - Get the local port
 * @sk: pointer to a struct inet_sock
 *
 * Returns the sport from a struct inet_sock in host order.
 */
function tcpmib_local_port:long(sk:long)
{
	return __tcp_sock_sport(sk)
}

/**
 * sfunction tcpmib_remote_port - Get the remote port
 * @sk: pointer to a struct inet_sock
 *
 * Returns the dport from a struct inet_sock in host order.
 */
function tcpmib_remote_port:long(sk:long)
{
	return __tcp_sock_dport(sk)
}

/**
 * probe tcpmib.ActiveOpens - Count an active opening of a socket
 * @sk:	pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @ActiveOpens (equivalent to SNMP's MIB
 * TCP_MIB_ACTIVEOPENS)
 */
probe
tcpmib.ActiveOpens = kernel.function("tcp_connect").return
{
	sk = @entry($sk);
	op = 1;
	if ( $return ) next;
        // definition in tcpipstat.stp
	key = tcpmib_filter_key(sk, op);
	if ( key ) ActiveOpens[key] += op;
}

/**
 * probe tcpmib.AttemptFails - Count a failed attempt to open a socket
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @AttemptFails (equivalent to SNMP's MIB
 * TCP_MIB_ATTEMPTFAILS)
 */
probe
tcpmib.AttemptFails = kernel.function("tcp_done").call ?
{
	sk = $sk;
	state = tcpmib_get_state($sk);
	op = 1;

	TCP_SYN_SENT = 2;
	TCP_SYN_RECV = 3;
	if( state == TCP_SYN_SENT || state == TCP_SYN_RECV){
		key = tcpmib_filter_key($sk, op);
		if ( key ) AttemptFails[key] += op;
	}
}

/**
 * probe tcpmib.CurrEstab - Update the count of open sockets
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @CurrEstab (equivalent to SNMP's MIB
 * TCP_MIB_CURRESTAB)
 */
probe
tcpmib.CurrEstab = kernel.function("tcp_set_state").call ?
{
	sk = $sk;
	state = $state;
	oldstate = tcpmib_get_state($sk);

	TCP_ESTABLISHED = 1;
	if ( oldstate == TCP_ESTABLISHED ) {
		op = -1;
		key = tcpmib_filter_key($sk, op);
		if ( key ) CurrEstab[key] += op;
	} else {
		if ( state == TCP_ESTABLISHED ) {
			op = 1;
			key = tcpmib_filter_key($sk, op);
			if ( key ) CurrEstab[key] += op;
		}
	}
}

/**
 * probe tcpmib.EstabResets - Count the reset of a socket
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @EstabResets (equivalent to SNMP's MIB
 * TCP_MIB_ESTABRESETS)
 */
probe
tcpmib.EstabResets = kernel.function("tcp_set_state").call ?
{
	sk = $sk;
        state = $state;
        oldstate = tcpmib_get_state($sk);
	op = 1;

	TCP_CLOSE = 7;
	TCP_CLOSE_WAIT = 8;
	TCP_ESTABLISHED = 1;
	if ( ( state == TCP_CLOSE ) && 
	   (oldstate == TCP_CLOSE_WAIT || oldstate == TCP_ESTABLISHED) ){
        	key = tcpmib_filter_key($sk, op);
		if ( key ) EstabResets[key] += op;
	}
}

/**
 * probe tcpmib.InSegs - Count an incoming tcp segment
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key() (or ipmib_filter_key() for tcp v4).
 * If the packet passes the filter is is
 * counted in the global @InSegs (equivalent to SNMP's MIB
 * TCP_MIB_INSEGS)
 */
probe
tcpmib.InSegs =
	kernel.function("__inet_lookup_established").return !,
	kernel.function("tcp_v4_rcv")
{
	if (@defined($return)) {
	        sk=$return
		key = tcpmib_filter_key($return, op)
	}
	else {
		# We don't know the sk in tcp_v4_rcv()
		sk=0
		key = ipmib_filter_key($skb, op, 0);
	}
	op=1
	if ( key ) InSegs[key] += op;
}

/**
 * probe tcpmib.OutRsts - Count the sending of a reset packet
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @OutRsts (equivalent to SNMP's MIB
 * TCP_MIB_OUTRSTS)
 */
probe
tcpmib.OutRsts = __tcpmib.OutRsts.*
{
}

@__private30 function _rtn_local:long ()
%{
        STAP_RETVALUE = RTN_LOCAL;
%}

@__private30 function _is_reset:long (skb:long)
%{ /* pure */
        struct tcphdr *th;
        struct sk_buff *skb = (struct sk_buff *)(uintptr_t)STAP_ARG_skb;
	struct tcphdr th_copy;

	#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,21)
		th = (struct tcphdr *)kread(&(skb->h.th));
	#else

	#ifdef NET_SKBUFF_DATA_USES_OFFSET
		th = (struct tcphdr *)(kread(&(skb->network_header)) + kread(&(skb->head)));
	#else
		th = (struct tcphdr *)kread(&(skb->network_header));
	#endif
	#endif

	// We'd like to kread the 'rst' field here. But, it is a
	// bitfield (and you can't take the address of a
	// bitfield). So, let's kread the entire tcphdr, then grab the
	// 'rst' field out of the copy. Luckily, the tcphdr is only 20
	// bytes long.
        memset (((void*)&th_copy), 0, sizeof(th_copy));
	kderef_buffer(((void *)&th_copy), th, sizeof(struct tcphdr));
	STAP_RETVALUE = th_copy.rst;
        CATCH_DEREF_FAULT();
%}

@__private30 function _tcpmib_input_route_type:long (skb:long)
%{ /* pure */
	struct rtable *rt;
	struct sk_buff *skb = (struct sk_buff *)(uintptr_t)STAP_ARG_skb;

	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)
		rt = (struct rtable *)kread(&(skb->_skb_refdst));
	#else

	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
		rt = (struct rtable *)kread(&(skb->_skb_dst));
	#else

	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
		rt = (struct rtable *)kread(&(skb->dst));
	#else
		rt = (struct rtable *)kread(&(skb->rtable));
	#endif
	#endif
	#endif

	if ( rt )
		STAP_RETVALUE = kread(&(rt->rt_type));
	else
		STAP_RETVALUE = RTN_UNSPEC;
	CATCH_DEREF_FAULT();
%}

/*
 * note:
 * tcp_v4_send_reset may be called with a NULL sk.
 * This happens when sending a reset in response to a syn 
 * when no socket exists (for example the service is not running).
 * Without a socket we can't count the reset.
 */
probe
__tcpmib.OutRsts.tcp_v4_send_reset = kernel.function("tcp_v4_send_reset")
{
        sk = @choose_defined($sk, 0)
        op = 1;
        if ( _is_reset($skb) ) next
        if (_tcpmib_input_route_type($skb) != _rtn_local() )
                next;
        key = (@defined($sk) ? tcpmib_filter_key(sk, op)
			     : ipmib_filter_key($skb, op, 1))
        if ( key ) OutRsts[key] += op;
}

probe
__tcpmib.OutRsts.tcp_send_active_reset =
	kernel.function("tcp_send_active_reset")
{
	/* Almost correct,
	 * If alloc_skb() fails it incorrectly bumps TCP_MIB_OUTRSTS,
	 */
	sk = $sk;
	op = 1;
	key = tcpmib_filter_key($sk, op);
        if ( key ) OutRsts[key] += op;
}

/**
 * probe tcpmib.OutSegs - Count the sending of a TCP segment
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @OutSegs (equivalent to SNMP's MIB
 * TCP_MIB_OUTSEGS)
 */
probe
tcpmib.OutSegs=kernel.function("ip_queue_xmit").return
{
	if ( $return < 0 ) next;
	op = 1;
	
        //  Only count the events with protocol IPPROTO_TCP,6.
	try {
		iphdr = __get_skb_iphdr(@entry($skb))
		if ( !(__ip_skb_proto(iphdr) == 6) ) next ;
	} catch { }
	try {
		sk = @entry($skb->sk);
		key = tcpmib_filter_key(sk, op);
	} catch { }
	if ( key ) OutSegs[key] += op;
}

/**
 * probe tcpmib.PassiveOpens - Count the passive creation of a socket
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @PassiveOpens (equivalent to SNMP's MIB
 * TCP_MIB_PASSIVEOPENS)
 */
probe
tcpmib.PassiveOpens=kernel.function("tcp_v4_syn_recv_sock").return
{
        sk = $return;
        op = 1;
        if ( !sk ) next;
        key = tcpmib_filter_key(sk, op);
        if ( key ) PassiveOpens[key] += op;
}

/**
 * probe tcpmib.RetransSegs - Count the retransmission of a TCP segment
 * @sk: pointer to the struct sock being acted on
 * @op: value to be added to the counter (default value of 1)
 *
 * The packet pointed to by @skb is filtered by the function
 * tcpmib_filter_key(). If the packet passes the filter is is
 * counted in the global @RetransSegs (equivalent to SNMP's MIB
 * TCP_MIB_RETRANSSEGS)
 */
probe
tcpmib.RetransSegs=kernel.function("tcp_retransmit_skb").return
{
	sk = @entry($sk);
	op = 1;
	if ( $return ) next;
	key = tcpmib_filter_key(sk, op);
	if ( key ) RetransSegs[key] += op;
}
