summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/net/tcpip.c
blob: 5ad982fd165086413c952a87410457f308865b2f (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
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <byteswap.h>
#include <ipxe/iobuf.h>
#include <ipxe/tables.h>
#include <ipxe/ipstat.h>
#include <ipxe/netdevice.h>
#include <ipxe/tcpip.h>

/** @file
 *
 * Transport-network layer interface
 *
 * This file contains functions and utilities for the
 * TCP/IP transport-network layer interface
 */

FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );

/**
 * Process a received TCP/IP packet
 *
 * @v iobuf		I/O buffer
 * @v netdev		Network device
 * @v tcpip_proto	Transport-layer protocol number
 * @v st_src		Partially-filled source address
 * @v st_dest		Partially-filled destination address
 * @v pshdr_csum	Pseudo-header checksum
 * @v stats		IP statistics
 * @ret rc		Return status code
 *
 * This function expects a transport-layer segment from the network
 * layer.  The network layer should fill in as much as it can of the
 * source and destination addresses (i.e. it should fill in the
 * address family and the network-layer addresses, but leave the ports
 * and the rest of the structures as zero).
 */
int tcpip_rx ( struct io_buffer *iobuf, struct net_device *netdev,
	       uint8_t tcpip_proto, struct sockaddr_tcpip *st_src,
	       struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum,
	       struct ip_statistics *stats ) {
	struct tcpip_protocol *tcpip;

	/* Hand off packet to the appropriate transport-layer protocol */
	for_each_table_entry ( tcpip, TCPIP_PROTOCOLS ) {
		if ( tcpip->tcpip_proto == tcpip_proto ) {
			DBG ( "TCP/IP received %s packet\n", tcpip->name );
			stats->in_delivers++;
			return tcpip->rx ( iobuf, netdev, st_src, st_dest,
					   pshdr_csum );
		}
	}

	DBG ( "Unrecognised TCP/IP protocol %d\n", tcpip_proto );
	stats->in_unknown_protos++;
	free_iob ( iobuf );
	return -EPROTONOSUPPORT;
}

/**
 * Find TCP/IP network-layer protocol
 *
 * @v st_dest		Destination address
 * @ret tcpip_net	TCP/IP network-layer protocol, or NULL if not found
 */
static struct tcpip_net_protocol *
tcpip_net_protocol ( struct sockaddr_tcpip *st_dest ) {
	struct tcpip_net_protocol *tcpip_net;

	for_each_table_entry ( tcpip_net, TCPIP_NET_PROTOCOLS ) {
		if ( tcpip_net->sa_family == st_dest->st_family )
			return tcpip_net;
	}

	DBG ( "Unrecognised TCP/IP address family %d\n", st_dest->st_family );
	return NULL;
}

/**
 * Transmit a TCP/IP packet
 *
 * @v iobuf		I/O buffer
 * @v tcpip_protocol	Transport-layer protocol
 * @v st_src		Source address, or NULL to use route default
 * @v st_dest		Destination address
 * @v netdev		Network device to use if no route found, or NULL
 * @v trans_csum	Transport-layer checksum to complete, or NULL
 * @ret rc		Return status code
 */
int tcpip_tx ( struct io_buffer *iobuf, struct tcpip_protocol *tcpip_protocol,
	       struct sockaddr_tcpip *st_src, struct sockaddr_tcpip *st_dest,
	       struct net_device *netdev, uint16_t *trans_csum ) {
	struct tcpip_net_protocol *tcpip_net;

	/* Hand off packet to the appropriate network-layer protocol */
	tcpip_net = tcpip_net_protocol ( st_dest );
	if ( tcpip_net ) {
		DBG ( "TCP/IP sending %s packet\n", tcpip_net->name );
		return tcpip_net->tx ( iobuf, tcpip_protocol, st_src, st_dest,
				       netdev, trans_csum );
	}

	free_iob ( iobuf );
	return -EAFNOSUPPORT;
}

/**
 * Determine transmitting network device
 *
 * @v st_dest		Destination address
 * @ret netdev		Network device, or NULL
 */
struct net_device * tcpip_netdev ( struct sockaddr_tcpip *st_dest ) {
	struct tcpip_net_protocol *tcpip_net;

	/* Hand off to the appropriate network-layer protocol */
	tcpip_net = tcpip_net_protocol ( st_dest );
	if ( tcpip_net )
		return tcpip_net->netdev ( st_dest );

	return NULL;
}

/**
 * Determine maximum transmission unit
 *
 * @v st_dest		Destination address
 * @ret mtu		Maximum transmission unit
 */
size_t tcpip_mtu ( struct sockaddr_tcpip *st_dest ) {
	struct tcpip_net_protocol *tcpip_net;
	struct net_device *netdev;
	size_t mtu;

	/* Find appropriate network-layer protocol */
	tcpip_net = tcpip_net_protocol ( st_dest );
	if ( ! tcpip_net )
		return 0;

	/* Find transmitting network device */
	netdev = tcpip_net->netdev ( st_dest );
	if ( ! netdev )
		return 0;

	/* Calculate MTU */
	mtu = ( netdev->max_pkt_len - netdev->ll_protocol->ll_header_len -
		tcpip_net->header_len );

	return mtu;
}

/**
 * Calculate continued TCP/IP checkum
 *
 * @v partial		Checksum of already-summed data, in network byte order
 * @v data		Data buffer
 * @v len		Length of data buffer
 * @ret cksum		Updated checksum, in network byte order
 *
 * Calculates a TCP/IP-style 16-bit checksum over the data block.  The
 * checksum is returned in network byte order.
 *
 * This function may be used to add new data to an existing checksum.
 * The function assumes that both the old data and the new data start
 * on even byte offsets; if this is not the case then you will need to
 * byte-swap either the input partial checksum, the output checksum,
 * or both.  Deciding which to swap is left as an exercise for the
 * interested reader.
 */
uint16_t generic_tcpip_continue_chksum ( uint16_t partial,
					 const void *data, size_t len ) {
	unsigned int cksum = ( ( ~partial ) & 0xffff );
	unsigned int value;
	unsigned int i;
	
	for ( i = 0 ; i < len ; i++ ) {
		value = * ( ( uint8_t * ) data + i );
		if ( i & 1 ) {
			/* Odd bytes: swap on little-endian systems */
			value = be16_to_cpu ( value );
		} else {
			/* Even bytes: swap on big-endian systems */
			value = le16_to_cpu ( value );
		}
		cksum += value;
		if ( cksum > 0xffff )
			cksum -= 0xffff;
	}
	
	return ( ~cksum );
}

/**
 * Calculate TCP/IP checkum
 *
 * @v data		Data buffer
 * @v len		Length of data buffer
 * @ret cksum		Checksum, in network byte order
 *
 * Calculates a TCP/IP-style 16-bit checksum over the data block.  The
 * checksum is returned in network byte order.
 */
uint16_t tcpip_chksum ( const void *data, size_t len ) {
	return tcpip_continue_chksum ( TCPIP_EMPTY_CSUM, data, len );
}

/**
 * Bind to local TCP/IP port
 *
 * @v st_local		Local TCP/IP socket address, or NULL
 * @v available		Function to check port availability
 * @ret port		Local port number, or negative error
 */
int tcpip_bind ( struct sockaddr_tcpip *st_local,
		 int ( * available ) ( int port ) ) {
	uint16_t flags = 0;
	uint16_t try_port = 0;
	uint16_t min_port;
	uint16_t max_port;
	unsigned int offset;
	unsigned int i;

	/* Extract parameters from local socket address */
	if ( st_local ) {
		flags = st_local->st_flags;
		try_port = ntohs ( st_local->st_port );
	}

	/* If an explicit port is specified, check its availability */
	if ( try_port )
		return available ( try_port );

	/* Otherwise, find an available port in the range [1,1023] or
	 * [1025,65535] as appropriate.
	 */
	min_port = ( ( ( ~flags ) & TCPIP_BIND_PRIVILEGED ) + 1 );
	max_port = ( ( flags & TCPIP_BIND_PRIVILEGED ) - 1 );
	offset = random();
	for ( i = 0 ; i <= max_port ; i++ ) {
		try_port = ( ( i + offset ) & max_port );
		if ( try_port < min_port )
			continue;
		if ( available ( try_port ) < 0 )
			continue;
		return try_port;
	}
	return -EADDRINUSE;
}