File: linux_wire.cc

package info (click to toggle)
upslug2 11-5
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 612 kB
  • sloc: sh: 3,430; cpp: 2,142; makefile: 7
file content (246 lines) | stat: -rw-r--r-- 8,091 bytes parent folder | download | duplicates (4)
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
/*-
 * linux_wire.cc
 *
 * Implementation of Wire for linux, may work on other UN*X systems
 * too...
 */
#include "config.h"

#if !HAVE_LIBPCAP
#include <cstring>
#include <cerrno>

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/select.h>

#include <net/ethernet.h>
#include <net/if.h>
#include <net/if_arp.h>

#include <netpacket/packet.h>

#include <unistd.h>
#include <sys/types.h>   /* Required for class EUID */

#include "nslu2_upgrade.h"

namespace NSLU2Upgrade {
	/* The basic class implemented to transmit and receive packets over the
	 * wire.
	 */
	class EthernetWire : public Wire {
	public:
		EthernetWire(int s, int hw_index, const unsigned char address[6]) :
			socket(s), broadcast(true) {
			/* Most of these fields aren't used in any given request,
			 * but it does no harm to fill them all correctly.
			 */
			nslu2To.sll_family = AF_PACKET;
			nslu2To.sll_protocol = NSLU2Protocol::UpgradeProtocol;
			nslu2To.sll_ifindex = hw_index;
			nslu2To.sll_hatype = ARPHRD_IEEE802;
			nslu2To.sll_pkttype = address ? PACKET_HOST : PACKET_BROADCAST;
			nslu2To.sll_halen = 6;
			/* The 255 gives the ethernet hardware broadcast address,
			 * overwrite this if a host address is provided.
			 */
			std::memset(nslu2To.sll_addr, 255, sizeof nslu2To.sll_addr);
			if (address) {
				broadcast = false;
				std::memcpy(nslu2To.sll_addr, address, 6);
			}

			/* This is set just in case of a call to LastAddress before
			 * Receive has succeeded - the result will be all 0's
			 */
			std::memset(nslu2From.sll_addr, 0, sizeof nslu2From.sll_addr);
		}

		virtual ~EthernetWire() {
			(void)close(socket);
		}

		/* Throws SendError on a fatal error. */
		virtual void Send(const void *packet, size_t length) {
			/* Set no flags (0) - we block on the sendto if
			 * required, the socket is *not* set O_NONBLOCK.
			 */
			while (sendto(socket, packet, length, 0,
					reinterpret_cast<sockaddr*>(&nslu2To),
					sizeof nslu2To) == (-1)) {
				if (errno != EINTR)
					throw SendError(errno);
			} 
		}

		/* Receive throws ReceiveError on a fatal error and must update
		 * size with the received packet size.  0 must be used to
		 * indicate failure to receive a packet (and this must not
		 * be fatal).  If timeout is greater than 0 the implementation
		 * should wait that number of microseconds until a packet is
		 * received or the timeout has expired (in which case a size
		 * of 0 must be returned).
		 */
		virtual void Receive(void *buffer, size_t &size, unsigned long timeout) {
			/* The socket is blocking (O_NONBLOCK is not set) therefore
			 * handle the 'block' option by polling.  Even if 'block'
			 * is true this call must not actually block - just
			 * time out - because the response packet we are waiting
			 * for may actually have been dropped.
			 */
			do {
				fd_set readfds;
				FD_ZERO(&readfds);
				FD_SET(socket, &readfds);
				
				/* Timeout as requested by the caller. */
				struct timeval tv;
				tv.tv_sec = timeout >> 20;
				tv.tv_usec = timeout & 0xfffff;
				if (tv.tv_usec >= 1000000)
					++tv.tv_sec, tv.tv_usec = 0;

				/* See if there is anything to read... */
				do {
					int fds(select(socket+1, &readfds, 0, 0, &tv));
					if (fds == 0) {
						size = 0;
						return;
					}
					if (fds != (-1))
						break;
					if (errno != EINTR)
						throw ReceiveError(errno);
				} while (1);

				/* There is something to read... */
				socklen_t resultSize(sizeof nslu2From);
				ssize_t result(recvfrom(socket, buffer, size, 0,
						reinterpret_cast<sockaddr*>(&nslu2From),
						&resultSize));
				if (result == (-1)) {
					if (errno != EINTR)
						throw ReceiveError(errno);
				} else if (broadcast || std::memcmp(nslu2To.sll_addr,
							nslu2From.sll_addr, 6) == 0) {
					/* otherwise this is not a packet for this
					 * program and it is just ignored.
					 */
					size = result;
					return;
				}
			} while (1);
		}

		/* Return the address of the last received packet.  This is
		 * an NSLU2 so the address is a 6 byte Ethernet hardware
		 * address.
		 */
		virtual void LastAddress(unsigned char address[6]) {
			std::memcpy(address, nslu2From.sll_addr, 6);
		}

	private:
		struct sockaddr_ll nslu2To;
		struct sockaddr_ll nslu2From;
		int                socket;
		bool               broadcast;
	};

	/* Class to set and reset the user id to the effective uid. */
	/* Requires unistd.h and sys/types.h */
	class EUID {
	public:
		EUID(int uid) : euid(::geteuid()) {
			if (uid != -1 && ::seteuid(uid) != 0)
				throw WireError(errno);
		}

		~EUID() {
			::seteuid(euid);
		}

	private:
		::uid_t euid;
	};

};


/* Make a new wire, which may be deleted with delete.  The
 * address should be a value (null terminated this time) returned
 * by LastAddress, if NULL the Wire will broadcast.  'device'
 * is the hardware device name to use - the value of the
 * --device parameter on the command line (if given).  If not
 *  given (NULL) a potentially useless default will be used.
 *
 *  The (from) mac is ignored in the Linux implementation since
 *  it can always be set correctly.
 */
NSLU2Upgrade::Wire *NSLU2Upgrade::Wire::MakeWire(const char *device,
		const unsigned char *mac, const unsigned char *address, int uid) {
	int packet_socket;
	struct ifreq device_interface;

		{
		EUID euid(uid);
		/* Obtain a datagram low level socket using the 'invented' NSLU2
		 * protocol number.  Change to the effective user id to do
		 * this (if given).
		 */
		packet_socket = socket(PF_PACKET, SOCK_DGRAM, NSLU2Protocol::UpgradeProtocol);
		if (packet_socket == (-1))
			throw WireError(errno);

		/* Check the device name.  If not given use 'eth0'. */
		if (device == NULL)
			device = "eth0";

		/* We are using a level which requires a hardware specific address,
		 * that's because the NSLU2 doesn't (for reasons which are far from
		 * obvious) implement a standard protocol for the upgrade, therefore
		 * there is no standard way of addressing the NSLU2.  Instead we must
		 * use the hardware, which on the NSLU2 is Ethernet, and which therefore
		 * has a 6 byte 'name'.
		 *
		 * What this means is that we need to be talking on an ethernet device;
		 * there ain't no way of getting a random ethernet packet onto some
		 * other network, because there is no way of mapping the address (which
		 * is an ethernet hardware id) into an appropriate address on another
		 * network.  (NOTE: 'tunnelling' stuff does this, it wraps the whole
		 * packet up inside another packet and sends it down the tunnel, it gets
		 * unwrapped at the other end, but that is transparent to this code.)
		 * 
		 * At this point we need an ethernet device to talk to.  Notice that this
		 * could, in theory, be a fake device - just so long as the NSLU2 has a
		 * six byte ethernet address to talk back to.  We look the given device
		 * name up on the socket.  (See netdevice(7) - this is linux specific)
		 *
		 * NOTE: if you are looking at this code and trying to port it the device
		 * stuff may be irrelevant, what you need to do is receive all packets
		 * with the protocol 0x8888 (NSLU2Protocol::UpgradeProtocol) from any
		 * *ethernet* MAC to implement the broadcast stuff and from a specific
		 * ethernet MAC to implement upgrade.  The broadcast stuff isn't necessary
		 * to implement a working upslug2 - because the ethernet MAC can be
		 * determined from the label on the bottom of an NSLU2, so the user can
		 * just be obliged to turn the damn box over.  And this is a better GUI.
		 */
		strncpy(device_interface.ifr_name, device, sizeof device_interface.ifr_name);
		device_interface.ifr_name[(sizeof device_interface.ifr_name)-1] = 0;

		if (ioctl(packet_socket, SIOCGIFINDEX, &device_interface) == (-1)) {
			/* This means the device name is bogus, because if we weren't
			 * euid 0 the socket call would have EACCESed above.
			 */
			const int err(errno);
			(void)close(packet_socket);
			throw WireError(err);
		}
	}

	/* This is enough to make a new wire. */
	return new EthernetWire(packet_socket, device_interface.ifr_ifindex, address);
}
#endif