+/*
+ * socket lookup function for linux.
+ * This code is used to associate uid, gid, jail/xid to packets,
+ * and store the info in a cache *ugp where they can be accessed quickly.
+ * The function returns 1 if the info is found, -1 otherwise.
+ *
+ * We do this only on selected protocols: TCP, ...
+ *
+ * The chain is the following
+ * sk_buff* sock* socket* file*
+ * skb -> sk ->sk_socket->file ->f_owner ->pid
+ * skb -> sk ->sk_socket->file ->f_uid (direct)
+ * skb -> sk ->sk_socket->file ->f_cred->fsuid (2.6.29+)
+ *
+ * Related headers:
+ * linux/skbuff.h struct skbuff
+ * net/sock.h struct sock
+ * linux/net.h struct socket
+ * linux/fs.h struct file
+ *
+ * With vserver we may have sk->sk_xid and sk->sk_nid that
+ * which we store in fw_groups[1] (matches O_JAIL) and fw_groups[2]
+ * (no matches yet)
+ *
+ * Note- for locally generated, outgoing packets we should not need
+ * need a lookup because the sk_buff already points to the socket where
+ * the info is.
+ */
+extern struct inet_hashinfo tcp_hashinfo;
+int
+linux_lookup(const int proto, const __be32 saddr, const __be16 sport,
+ const __be32 daddr, const __be16 dport,
+ struct sk_buff *skb, int dir, struct bsd_ucred *u)
+{
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,0)
+ return -1;
+#else
+ struct sock *sk;
+ int ret = -1; /* default return value */
+ int st = -1; /* state */
+
+
+ if (proto != IPPROTO_TCP) /* XXX extend for UDP */
+ return -1;
+
+ if ((dir ? (void *)skb->dst : (void *)skb->dev) == NULL) {
+ panic(" -- this should not happen\n");
+ return -1;
+ }
+
+ if (skb->sk) {
+ sk = skb->sk;
+ } else {
+ /*
+ * Try a lookup. On a match, sk has a refcount that we must
+ * release on exit (we know it because skb->sk = NULL).
+ *
+ * inet_lookup above 2.6.24 has an additional 'net' parameter
+ * so we use a macro to conditionally supply it.
+ * swap dst and src depending on the direction.
+ */
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24)
+#define _OPT_NET_ARG
+#else
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
+/* there is no dev_net() on 2.6.25 */
+#define _OPT_NET_ARG (skb->dev->nd_net),
+#else /* 2.6.26 and above */
+#define _OPT_NET_ARG dev_net(skb->dev),
+#endif
+#endif
+ sk = (dir) ? /* dir != 0 on output */
+ inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+ daddr, dport, saddr, sport, // match outgoing
+ inet_iif(skb)) :
+ inet_lookup(_OPT_NET_ARG &tcp_hashinfo,
+ saddr, sport, daddr, dport, // match incoming
+ skb->dev->ifindex);
+#undef _OPT_NET_ARG
+
+ if (sk == NULL) /* no match, nothing to be done */
+ return -1;
+ }
+ ret = 1; /* retrying won't make things better */
+ st = sk->sk_state;
+#ifdef CONFIG_VSERVER
+ u->xid = sk->sk_xid;
+ u->nid = sk->sk_nid;
+#else
+ u->xid = u->nid = 0;
+#endif
+ /*
+ * Exclude tcp states where sk points to a inet_timewait_sock which
+ * has no sk_socket field (surely TCP_TIME_WAIT, perhaps more).
+ * To be safe, use a whitelist and not a blacklist.
+ * Before dereferencing sk_socket grab a lock on sk_callback_lock.
+ *
+ * Once again we need conditional code because the UID and GID
+ * location changes between kernels.
+ */
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28)
+/* use the current's real uid/gid */
+#define _CURR_UID f_uid
+#define _CURR_GID f_gid
+#else /* 2.6.29 and above */
+/* use the current's file access real uid/gid */
+#define _CURR_UID f_cred->fsuid
+#define _CURR_GID f_cred->fsgid
+#endif
+
+#define GOOD_STATES ( \
+ (1<<TCP_LISTEN) | (1<<TCP_SYN_RECV) | (1<<TCP_SYN_SENT) | \
+ (1<<TCP_ESTABLISHED) | (1<<TCP_FIN_WAIT1) | (1<<TCP_FIN_WAIT2) )
+ // surely exclude TCP_CLOSE, TCP_TIME_WAIT, TCP_LAST_ACK
+ // uncertain TCP_CLOSE_WAIT and TCP_CLOSING
+
+ if ((1<<st) & GOOD_STATES) {
+ read_lock_bh(&sk->sk_callback_lock);
+ if (sk->sk_socket && sk->sk_socket->file) {
+ u->uid = sk->sk_socket->file->_CURR_UID;
+ u->gid = sk->sk_socket->file->_CURR_GID;
+ }
+ read_unlock_bh(&sk->sk_callback_lock);
+ } else {
+ u->uid = u->gid = 0;
+ }
+ if (!skb->sk) /* return the reference that came from the lookup */
+ sock_put(sk);
+#undef GOOD_STATES
+#undef _CURR_UID
+#undef _CURR_GID
+ return ret;
+
+#endif /* LINUX > 2.4 */
+}