Browse Source

[peerdist] Add support for constructing and decoding discovery messages

Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 8 years ago
parent
commit
51b99d8bc8
3 changed files with 334 additions and 0 deletions
  1. 1
    0
      src/include/ipxe/errfile.h
  2. 47
    0
      src/include/ipxe/pccrd.h
  3. 286
    0
      src/net/pccrd.c

+ 1
- 0
src/include/ipxe/errfile.h View File

@@ -246,6 +246,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
246 246
 #define ERRFILE_rndis			( ERRFILE_NET | 0x003d0000 )
247 247
 #define ERRFILE_pccrc			( ERRFILE_NET | 0x003e0000 )
248 248
 #define ERRFILE_stp			( ERRFILE_NET | 0x003f0000 )
249
+#define ERRFILE_pccrd			( ERRFILE_NET | 0x00400000 )
249 250
 
250 251
 #define ERRFILE_image		      ( ERRFILE_IMAGE | 0x00000000 )
251 252
 #define ERRFILE_elf		      ( ERRFILE_IMAGE | 0x00010000 )

+ 47
- 0
src/include/ipxe/pccrd.h View File

@@ -0,0 +1,47 @@
1
+#ifndef _IPXE_PCCRD_H
2
+#define _IPXE_PCCRD_H
3
+
4
+/** @file
5
+ *
6
+ * Peer Content Caching and Retrieval: Discovery Protocol [MS-PCCRD]
7
+ *
8
+ */
9
+
10
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
11
+
12
+/** PeerDist discovery port */
13
+#define PEERDIST_DISCOVERY_PORT 3702
14
+
15
+/** PeerDist discovery IPv4 address (239.255.255.250) */
16
+#define PEERDIST_DISCOVERY_IPV4 \
17
+	( ( 239 << 24 ) | ( 255 << 16 ) | ( 255 << 8 ) | ( 250 << 0 ) )
18
+
19
+/** PeerDist discovery IPv6 address (ff02::c) */
20
+#define PEERDIST_DISCOVERY_IPV6 \
21
+	{ 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xc }
22
+
23
+/** A PeerDist discovery reply block count */
24
+struct peerdist_discovery_block_count {
25
+	/** Count (as an eight-digit hex value) */
26
+	char hex[8];
27
+} __attribute__ (( packed ));
28
+
29
+/** A PeerDist discovery reply */
30
+struct peerdist_discovery_reply {
31
+	/** List of segment ID strings
32
+	 *
33
+	 * The list is terminated with a zero-length string.
34
+	 */
35
+	char *ids;
36
+	/** List of peer locations
37
+	 *
38
+	 * The list is terminated with a zero-length string.
39
+	 */
40
+	char *locations;
41
+};
42
+
43
+extern char * peerdist_discovery_request ( const char *uuid, const char *id );
44
+extern int peerdist_discovery_reply ( char *data, size_t len,
45
+				      struct peerdist_discovery_reply *reply );
46
+
47
+#endif /* _IPXE_PCCRD_H */

+ 286
- 0
src/net/pccrd.c View File

@@ -0,0 +1,286 @@
1
+/*
2
+ * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3
+ *
4
+ * This program is free software; you can redistribute it and/or
5
+ * modify it under the terms of the GNU General Public License as
6
+ * published by the Free Software Foundation; either version 2 of the
7
+ * License, or (at your option) any later version.
8
+ *
9
+ * This program is distributed in the hope that it will be useful, but
10
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
+ * General Public License for more details.
13
+ *
14
+ * You should have received a copy of the GNU General Public License
15
+ * along with this program; if not, write to the Free Software
16
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ * 02110-1301, USA.
18
+ *
19
+ * You can also choose to distribute this program under the terms of
20
+ * the Unmodified Binary Distribution Licence (as given in the file
21
+ * COPYING.UBDL), provided that you have satisfied its requirements.
22
+ */
23
+
24
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
+
26
+#include <stddef.h>
27
+#include <stdlib.h>
28
+#include <stdio.h>
29
+#include <string.h>
30
+#include <ctype.h>
31
+#include <errno.h>
32
+#include <assert.h>
33
+#include <ipxe/pccrd.h>
34
+
35
+/** @file
36
+ *
37
+ * Peer Content Caching and Retrieval: Discovery Protocol [MS-PCCRD]
38
+ *
39
+ * This protocol manages to ingeniously combine the excessive
40
+ * verbosity of XML with a paucity of actual information.  For
41
+ * example: even in version 2.0 of the protocol it is still not
42
+ * possible to discover which peers hold a specific block within a
43
+ * given segment.
44
+ *
45
+ * For added bonus points, version 1.0 of the protocol is specified to
46
+ * use a case-sensitive string comparison (for SHA2 digest values) but
47
+ * nothing specifies whether the strings in question should be in
48
+ * upper or lower case.  There are example strings given in the
49
+ * specification, but the author skilfully manages to leave the issue
50
+ * unresolved by using the somewhat implausible digest value of
51
+ * "0200000000000000000000000000000000000000000000000000000000000000".
52
+ *
53
+ * Just in case you were thinking that the silver lining of the choice
54
+ * to use an XML-based protocol would be the ability to generate and
55
+ * process messages with standard tools, version 2.0 of the protocol
56
+ * places most of the critical information inside a Base64-encoded
57
+ * custom binary data structure.  Within an XML element, naturally.
58
+ *
59
+ * I hereby announce this specification to be the 2015 winner of the
60
+ * prestigious "UEFI HII API" award for incompetent design.
61
+ */
62
+
63
+/** Discovery request format */
64
+#define PEERDIST_DISCOVERY_REQUEST					      \
65
+	"<?xml version=\"1.0\" encoding=\"utf-8\"?>"			      \
66
+	"<soap:Envelope "						      \
67
+	    "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "	      \
68
+	    "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " \
69
+	    "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" "  \
70
+	    "xmlns:PeerDist=\"http://schemas.microsoft.com/p2p/"	      \
71
+			     "2007/09/PeerDistributionDiscovery\">"	      \
72
+	  "<soap:Header>"						      \
73
+	    "<wsa:To>"							      \
74
+	      "urn:schemas-xmlsoap-org:ws:2005:04:discovery"		      \
75
+	    "</wsa:To>"							      \
76
+	    "<wsa:Action>"						      \
77
+	      "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe"	      \
78
+	    "</wsa:Action>"						      \
79
+	    "<wsa:MessageID>"						      \
80
+	      "urn:uuid:%s"						      \
81
+	    "</wsa:MessageID>"						      \
82
+	  "</soap:Header>"						      \
83
+	  "<soap:Body>"							      \
84
+	    "<wsd:Probe>"						      \
85
+	      "<wsd:Types>"						      \
86
+		"PeerDist:PeerDistData"					      \
87
+	      "</wsd:Types>"						      \
88
+	      "<wsd:Scopes MatchBy=\"http://schemas.xmlsoap.org/ws/"	      \
89
+				    "2005/04/discovery/strcmp0\">"	      \
90
+		"%s"							      \
91
+	      "</wsd:Scopes>"						      \
92
+	    "</wsd:Probe>"						      \
93
+	  "</soap:Body>"						      \
94
+	"</soap:Envelope>"
95
+
96
+/**
97
+ * Construct discovery request
98
+ *
99
+ * @v uuid		Message UUID string
100
+ * @v id		Segment identifier string
101
+ * @ret request		Discovery request, or NULL on failure
102
+ *
103
+ * The request is dynamically allocated; the caller must eventually
104
+ * free() the request.
105
+ */
106
+char * peerdist_discovery_request ( const char *uuid, const char *id ) {
107
+	char *request;
108
+	int len;
109
+
110
+	/* Construct request */
111
+	len = asprintf ( &request, PEERDIST_DISCOVERY_REQUEST, uuid, id );
112
+	if ( len < 0 )
113
+		return NULL;
114
+
115
+	return request;
116
+}
117
+
118
+/**
119
+ * Locate discovery reply tag
120
+ *
121
+ * @v data		Reply data (not NUL-terminated)
122
+ * @v len		Length of reply data
123
+ * @v tag		XML tag
124
+ * @ret found		Found tag (or NULL if not found)
125
+ */
126
+static char * peerdist_discovery_reply_tag ( char *data, size_t len,
127
+					     const char *tag ) {
128
+	size_t tag_len = strlen ( tag );
129
+
130
+	/* Search, allowing for the fact that the reply data is not
131
+	 * cleanly NUL-terminated and may contain embedded NULs due to
132
+	 * earlier parsing.
133
+	 */
134
+	for ( ; len >= tag_len ; data++, len-- ) {
135
+		if ( strncmp ( data, tag, tag_len ) == 0 )
136
+			return data;
137
+	}
138
+	return NULL;
139
+}
140
+
141
+/**
142
+ * Locate discovery reply values
143
+ *
144
+ * @v data		Reply data (not NUL-terminated, will be modified)
145
+ * @v len		Length of reply data
146
+ * @v name		XML tag name
147
+ * @ret values		Tag values (or NULL if not found)
148
+ *
149
+ * The reply data is modified by adding NULs and moving characters as
150
+ * needed to produce a NUL-separated list of values, terminated with a
151
+ * zero-length string.
152
+ *
153
+ * This is not supposed to be a full XML parser; it's supposed to
154
+ * include just enough functionality to allow PeerDist discovery to
155
+ * work with existing implementations.
156
+ */
157
+static char * peerdist_discovery_reply_values ( char *data, size_t len,
158
+						const char *name ) {
159
+	char buf[ 2 /* "</" */ + strlen ( name ) + 1 /* ">" */ + 1 /* NUL */ ];
160
+	char *open;
161
+	char *close;
162
+	char *start;
163
+	char *end;
164
+	char *in;
165
+	char *out;
166
+	char c;
167
+
168
+	/* Locate opening tag */
169
+	snprintf ( buf, sizeof ( buf ), "<%s>", name );
170
+	open = peerdist_discovery_reply_tag ( data, len, buf );
171
+	if ( ! open )
172
+		return NULL;
173
+	start = ( open + strlen ( buf ) );
174
+	len -= ( start - data );
175
+	data = start;
176
+
177
+	/* Locate closing tag */
178
+	snprintf ( buf, sizeof ( buf ), "</%s>", name );
179
+	close = peerdist_discovery_reply_tag ( data, len, buf );
180
+	if ( ! close )
181
+		return NULL;
182
+	assert ( close >= open );
183
+	end = close;
184
+
185
+	/* Strip initial whitespace, convert other whitespace
186
+	 * sequences to single NULs, add terminating pair of NULs.
187
+	 * This will probably overwrite part of the closing tag.
188
+	 */
189
+	for ( in = start, out = start ; in < end ; in++ ) {
190
+		c = *in;
191
+		if ( isspace ( c ) ) {
192
+			if ( ( out > start ) && ( out[-1] != '\0' ) )
193
+				*(out++) = '\0';
194
+		} else {
195
+			*(out++) = c;
196
+		}
197
+	}
198
+	*(out++) = '\0';
199
+	*(out++) = '\0';
200
+	assert ( out < ( close + strlen ( buf ) ) );
201
+
202
+	return start;
203
+}
204
+
205
+/**
206
+ * Parse discovery reply
207
+ *
208
+ * @v data		Reply data (not NUL-terminated, will be modified)
209
+ * @v len		Length of reply data
210
+ * @v reply		Discovery reply to fill in
211
+ * @ret rc		Return status code
212
+ *
213
+ * The discovery reply includes pointers to strings within the
214
+ * modified reply data.
215
+ */
216
+int peerdist_discovery_reply ( char *data, size_t len,
217
+			       struct peerdist_discovery_reply *reply ) {
218
+	static const struct peerdist_discovery_block_count zcount = {
219
+		.hex = "00000000",
220
+	};
221
+	struct peerdist_discovery_block_count *count;
222
+	unsigned int max;
223
+	unsigned int i;
224
+	char *scopes;
225
+	char *xaddrs;
226
+	char *blockcount;
227
+	char *in;
228
+	char *out;
229
+	size_t skip;
230
+
231
+	/* Find <wsd:Scopes> tag */
232
+	scopes = peerdist_discovery_reply_values ( data, len, "wsd:Scopes" );
233
+	if ( ! scopes ) {
234
+		DBGC ( reply, "PCCRD %p missing <wsd:Scopes> tag\n", reply );
235
+		return -ENOENT;
236
+	}
237
+
238
+	/* Find <wsd:XAddrs> tag */
239
+	xaddrs = peerdist_discovery_reply_values ( data, len, "wsd:XAddrs" );
240
+	if ( ! xaddrs ) {
241
+		DBGC ( reply, "PCCRD %p missing <wsd:XAddrs> tag\n", reply );
242
+		return -ENOENT;
243
+	}
244
+
245
+	/* Find <PeerDist:BlockCount> tag */
246
+	blockcount = peerdist_discovery_reply_values ( data, len,
247
+						       "PeerDist:BlockCount" );
248
+	if ( ! blockcount ) {
249
+		DBGC ( reply, "PCCRD %p missing <PeerDist:BlockCount> tag\n",
250
+		       reply );
251
+		return -ENOENT;
252
+	}
253
+
254
+	/* Determine maximum number of segments (according to number
255
+	 * of entries in the block count list).
256
+	 */
257
+	max = ( strlen ( blockcount ) / sizeof ( *count ) );
258
+	count = container_of ( blockcount,
259
+			       struct peerdist_discovery_block_count, hex[0] );
260
+
261
+	/* Eliminate any segments with a zero block count */
262
+	for ( i = 0, in = scopes, out = scopes ; *in ; i++, in += skip ) {
263
+
264
+		/* Fail if we have overrun the maximum number of segments */
265
+		if ( i >= max ) {
266
+			DBGC ( reply, "PCCRD %p too many segment IDs\n",
267
+			       reply );
268
+			return -EPROTO;
269
+		}
270
+
271
+		/* Delete segment if block count is zero */
272
+		skip = ( strlen ( in ) + 1 /* NUL */ );
273
+		if ( memcmp ( count[i].hex, zcount.hex,
274
+			      sizeof ( zcount.hex ) ) == 0 )
275
+			continue;
276
+		strcpy ( out, in );
277
+		out += skip;
278
+	}
279
+	out[0] = '\0'; /* Ensure list is terminated with a zero-length string */
280
+
281
+	/* Fill in discovery reply */
282
+	reply->ids = scopes;
283
+	reply->locations = xaddrs;
284
+
285
+	return 0;
286
+}

Loading…
Cancel
Save