summaryrefslogtreecommitdiffstats
path: root/src/extra/tcp.c
blob: 8119843b232b77f8ef3fbf2383dfb0de958794a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/*
 * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This code has been sponsored by Vyatta Inc. <http://www.vyatta.com>
 */

#include <stdio.h>
#include <string.h> /* for memcpy */
#include <stdbool.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#define _GNU_SOURCE
#include <netinet/tcp.h>

#include <libnetfilter_queue/libnetfilter_queue.h>
#include <libnetfilter_queue/libnetfilter_queue_tcp.h>
#include <libnetfilter_queue/libnetfilter_queue_ipv4.h>
#include <libnetfilter_queue/libnetfilter_queue_ipv6.h>
#include <libnetfilter_queue/pktbuff.h>

#include "internal.h"

/**
 * \defgroup tcp TCP helper functions
 * @{
 */

/**
 * nfq_tcp_get - get the TCP header
 * \param pktb: pointer to user-space network packet buffer
 *
 * This function returns NULL if an invalid TCP header is found. On success,
 * it returns the TCP header.
 *
 * \note You have to call nfq_ip_set_transport_header or
 * nfq_ip6_set_transport_header first to access the TCP header.
 */
EXPORT_SYMBOL
struct tcphdr *nfq_tcp_get_hdr(struct pkt_buff *pktb)
{
	if (pktb->transport_header == NULL)
		return NULL;

	/* No room for the TCP header. */
	if (pktb->tail - pktb->transport_header < sizeof(struct tcphdr))
		return NULL;

	return (struct tcphdr *)pktb->transport_header;
}

/**
 * nfq_tcp_get_payload - get the TCP packet payload
 * \param tcph: pointer to the TCP header
 * \param pktb: pointer to user-space network packet buffer
 */
EXPORT_SYMBOL
void *nfq_tcp_get_payload(struct tcphdr *tcph, struct pkt_buff *pktb)
{
	unsigned int len = tcph->doff * 4;

	/* TCP packet is too short */
	if (len < sizeof(struct tcphdr))
		return NULL;

	/* malformed TCP data offset. */
	if (pktb->transport_header + len > pktb->tail)
		return NULL;

	return pktb->transport_header + len;
}

/**
 * nfq_tcp_get_payload_len - get the tcp packet payload
 * \param tcph: pointer to the TCP header
 * \param pktb: pointer to user-space network packet buffer
 */
EXPORT_SYMBOL
unsigned int nfq_tcp_get_payload_len(struct tcphdr *tcph, struct pkt_buff *pktb)
{
	return pktb->tail - pktb->transport_header;
}

/**
 * nfq_tcp_set_checksum_ipv4 - computes IPv4/TCP packet checksum
 * \param tcph: pointer to the TCP header
 * \param iph: pointer to the IPv4 header
 */
EXPORT_SYMBOL
void nfq_tcp_compute_checksum_ipv4(struct tcphdr *tcph, struct iphdr *iph)
{
	/* checksum field in header needs to be zero for calculation. */
	tcph->check = 0;
	tcph->check = nfq_checksum_tcpudp_ipv4(iph, IPPROTO_TCP);
}

/**
 * nfq_tcp_set_checksum_ipv6 - computes IPv6/TCP packet checksum
 * \param tcph: pointer to the TCP header
 * \param iph: pointer to the IPv6 header
 */
EXPORT_SYMBOL
void nfq_tcp_compute_checksum_ipv6(struct tcphdr *tcph, struct ip6_hdr *ip6h)
{
	/* checksum field in header needs to be zero for calculation. */
	tcph->check = 0;
	tcph->check = nfq_checksum_tcpudp_ipv6(ip6h, tcph, IPPROTO_TCP);
}

/*
 *	The union cast uses a gcc extension to avoid aliasing problems
 *  (union is compatible to any of its members)
 *  This means this part of the code is -fstrict-aliasing safe now.
 */
union tcp_word_hdr {
	struct tcphdr hdr;
	uint32_t  words[5];
};

#define tcp_flag_word(tp) ( ((union tcp_word_hdr *)(tp))->words[3])

/**
 * nfq_pkt_snprintf_tcp_hdr - print tcp header into one buffer in a humnan
 * readable way
 * \param buf: pointer to buffer that is used to print the object
 * \param size: size of the buffer (or remaining room in it).
 * \param tcp: pointer to a valid tcp header.
 *
 */
EXPORT_SYMBOL
int nfq_tcp_snprintf(char *buf, size_t size, const struct tcphdr *tcph)
{
	int ret, len = 0;

#define TCP_RESERVED_BITS htonl(0x0F000000)

	ret = snprintf(buf, size, "SPT=%u DPT=%u SEQ=%u ACK=%u "
				   "WINDOW=%u RES=0x%02x ",
			ntohs(tcph->source), ntohs(tcph->dest),
			ntohl(tcph->seq), ntohl(tcph->ack_seq),
			ntohs(tcph->window),
			(uint8_t)
			(ntohl(tcp_flag_word(tcph) & TCP_RESERVED_BITS) >> 22));
	len += ret;

	if (tcph->urg) {
		ret = snprintf(buf+len, size-len, "URG ");
		len += ret;
	}
	if (tcph->ack) {
		ret = snprintf(buf+len, size-len, "ACK ");
		len += ret;
	}
	if (tcph->psh) {
		ret = snprintf(buf+len, size-len, "PSH ");
		len += ret;
	}
	if (tcph->rst) {
		ret = snprintf(buf+len, size-len, "RST ");
		len += ret;
	}
	if (tcph->syn) {
		ret = snprintf(buf+len, size-len, "SYN ");
		len += ret;
	}
	if (tcph->fin) {
		ret = snprintf(buf+len, size-len, "FIN ");
		len += ret;
	}
	/* XXX: Not TCP options implemented yet, sorry. */

	return ret;
}

/**
 * nfq_tcp_mangle_ipv4 - mangle TCP/IPv4 packet buffer
 * \param pktb: pointer to network packet buffer
 * \param match_offset: offset to content that you want to mangle
 * \param match_len: length of the existing content you want to mangle
 * \param rep_buffer: pointer to data you want to use to replace current content
 * \param rep_len: length of data you want to use to replace current content
 *
 * \note This function recalculates the IPv4 and TCP checksums for you.
 */
EXPORT_SYMBOL
int nfq_tcp_mangle_ipv4(struct pkt_buff *pkt,
			unsigned int match_offset, unsigned int match_len,
			const char *rep_buffer, unsigned int rep_len)
{
	struct iphdr *iph;
	struct tcphdr *tcph;

	iph = (struct iphdr *)pkt->network_header;
	tcph = (struct tcphdr *)(pkt->network_header + iph->ihl*4);

	if (!nfq_ip_mangle(pkt, iph->ihl*4 + tcph->doff*4,
				match_offset, match_len, rep_buffer, rep_len))
		return 0;

	nfq_tcp_compute_checksum_ipv4(tcph, iph);

	return 1;
}

/**
 * nfq_tcp_mangle_ipv6 - Mangle TCP/IPv6 packet buffer
 * \param pktb: Pointer to network packet buffer
 * \param match_offset: Offset from start of TCP data of content that you want
 * to mangle
 * \param match_len: Length of the existing content you want to mangle
 * \param rep_buffer: Pointer to data you want to use to replace current content
 * \param rep_len: Length of data you want to use to replace current content
 * \returns 1 for success and 0 for failure. See pktb_mangle() for failure case
 * \note This function updates the IPv6 length and recalculates the TCP
 * checksum for you.
 * \warning After changing the length of a TCP message, the application will
 * need to mangle sequence numbers in both directions until another change
 * puts them in sync again
 */
EXPORT_SYMBOL
int nfq_tcp_mangle_ipv6(struct pkt_buff *pktb,
			unsigned int match_offset, unsigned int match_len,
			const char *rep_buffer, unsigned int rep_len)
{
	struct ip6_hdr *ip6h;
	struct tcphdr *tcph;

	ip6h = (struct ip6_hdr *)pktb->network_header;
	tcph = (struct tcphdr *)(pktb->transport_header);
	if (!tcph)
		return 0;

	if (!nfq_ip6_mangle(pktb,
			   pktb->transport_header - pktb->network_header +
			   tcph->doff * 4,
			   match_offset, match_len, rep_buffer, rep_len))
		return 0;

	nfq_tcp_compute_checksum_ipv6(tcph, ip6h);

	return 1;
}

/**
 * @}
 */