xref: /haiku/src/add-ons/kernel/network/devices/dialup/dialup.cpp (revision 8d25a74c68ef03bec413ddb60f8b9a1ea2e906aa)
1 /*
2  * Copyright 2010, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Philippe Houdoin, <phoudoin %at% haiku-os %dot% org>
7  */
8 
9 
10 #include <net_buffer.h>
11 #include <net_device.h>
12 #include <net_stack.h>
13 
14 #include <KernelExport.h>
15 
16 #include <errno.h>
17 #include <net/if.h>
18 #include <net/if_dl.h>
19 #include <net/if_media.h>
20 #include <net/if_types.h>
21 #include <new>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <termios.h>
26 #include <sys/uio.h>
27 
28 
29 #define HDLC_FLAG_SEQUENCE	0x7e
30 #define HDLC_CONTROL_ESCAPE	0x7d
31 
32 #define HDLC_ALL_STATIONS	0xff
33 #define HDLC_UI				0x03
34 
35 #define HDLC_HEADER_LENGTH	4
36 
37 
38 enum dialup_state {
39 	DOWN,
40 	DIALING,
41 	UP,
42 	HANGINGUP
43 };
44 
45 struct dialup_device : net_device {
46 	int				fd;
47 	struct termios	line_config;
48 	dialup_state 	state;
49 	bigtime_t		last_closing_flag_sequence_time;
50 	bool			data_mode;
51 	char			init_string[64];
52 	char			dial_string[64];
53 	char			escape_string[8];
54 	bigtime_t		escape_silence;
55 	char			hangup_string[16];
56 };
57 
58 net_buffer_module_info* gBufferModule;
59 static net_stack_module_info* sStackModule;
60 
61 
62 //	#pragma mark -
63 
64 
65 static status_t
66 switch_to_command_mode(dialup_device* device)
67 {
68 	if (device->state != UP || !device->data_mode)
69 		return B_ERROR;
70 
71 	snooze(device->escape_silence);
72 
73 	ssize_t size = write(device->fd, device->escape_string,
74 			strlen(device->escape_string));
75 	if (size != (ssize_t)strlen(device->escape_string))
76 		return B_IO_ERROR;
77 
78 	snooze(device->escape_silence);
79 	device->data_mode = false;
80 	return B_OK;
81 }
82 
83 #if 0
84 static status_t
85 switch_to_data_mode(dialup_device* device)
86 {
87 	if (device->state != UP)
88 		return B_OK;
89 
90 	// TODO: check if it's needed, as these days any
91 	// escaped AT commands switch back to data mode automatically
92 	// after their completion...
93 	ssize_t size = write(device->fd, "ATO", 3);
94 	if (size != 3)
95 		return B_IO_ERROR;
96 
97 	device->data_mode = true;
98 	return B_OK;
99 }
100 #endif
101 
102 static status_t
103 send_command(dialup_device* device, const char* command)
104 {
105 	status_t status;
106 	if (device->data_mode) {
107 		status = switch_to_command_mode(device);
108 		if (status != B_OK)
109 			return status;
110 	}
111 
112 	ssize_t bytesWritten = write(device->fd, command, strlen(command));
113 	if (bytesWritten != (ssize_t)strlen(command))
114 		return B_IO_ERROR;
115 
116 	if (write(device->fd, "\r", 1) != 1)
117 		return B_IO_ERROR;
118 
119 	return B_OK;
120 }
121 
122 
123 static status_t
124 read_command_reply(dialup_device* device, const char* command,
125 	char* reply, int replyMaxSize)
126 {
127 	if (device->data_mode)
128 		return B_ERROR;
129 
130 	int i = 0;
131 	while (i < replyMaxSize) {
132 
133 		ssize_t bytesRead = read(device->fd, &reply[i], 1);
134 		if (bytesRead != 1)
135 			return B_IO_ERROR;
136 
137 		if (reply[i] == '\n') {
138 			// filter linefeed char
139 			continue;
140 		}
141 
142 		if (reply[i] == '\r') {
143 			reply[i] = '\0';
144 
145 			// is command reply or command echo (if any) ?
146 			if (!strcasecmp(reply, command))
147 				return B_OK;
148 
149 			// It's command echo line. Just ignore it.
150 			i = 0;
151 			continue;
152 		}
153 		i++;
154 	}
155 
156 	// replyMaxSize not large enough to store the full reply line.
157 	return B_NO_MEMORY;
158 }
159 
160 
161 static status_t
162 hangup(dialup_device* device)
163 {
164 	if (device->state != UP)
165 		return B_ERROR;
166 
167 	// TODO: turn device's DTR down instead. Or do that too after sending command
168 	char reply[8];
169 
170 	if (send_command(device, device->hangup_string) != B_OK
171 		|| read_command_reply(device, device->hangup_string,
172 			reply, sizeof(reply)) != B_OK
173 		|| strcmp(reply, "OK"))
174 		return B_ERROR;
175 
176 	device->state = DOWN;
177 	return B_OK;
178 }
179 
180 
181 //	#pragma mark -
182 
183 
184 status_t
185 dialup_init(const char* name, net_device** _device)
186 {
187 	// make sure this is a device in /dev/ports
188 	if (strncmp(name, "/dev/ports/", 11))
189 		return B_BAD_VALUE;
190 
191 	status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule);
192 	if (status < B_OK)
193 		return status;
194 
195 	dialup_device* device = new (std::nothrow)dialup_device;
196 	if (device == NULL) {
197 		put_module(NET_BUFFER_MODULE_NAME);
198 		return B_NO_MEMORY;
199 	}
200 
201 	memset(device, 0, sizeof(dialup_device));
202 
203 	strcpy(device->name, name);
204 	device->flags = IFF_POINTOPOINT;
205 	device->type = IFT_PPP; // this device handle RFC 1331 frame format only
206 	device->mtu = 1500;
207 	device->media = 0;
208 	device->header_length = HDLC_HEADER_LENGTH;
209 
210 	device->fd = -1;
211 	device->state = DOWN;
212 	device->data_mode = false;
213 	device->last_closing_flag_sequence_time = 0;
214 
215 	// default AT strings
216 	strncpy(device->init_string, "ATZ", sizeof(device->init_string));
217 	strncpy(device->dial_string, "ATDT", sizeof(device->dial_string));
218 	strncpy(device->hangup_string, "ATH0", sizeof(device->hangup_string));
219 
220 	strncpy(device->escape_string, "+++", sizeof(device->escape_string));
221 	device->escape_silence = 1000000;
222 
223 	*_device = device;
224 	return B_OK;
225 }
226 
227 
228 status_t
229 dialup_uninit(net_device* _device)
230 {
231 	dialup_device* device = (dialup_device*)_device;
232 	delete device;
233 
234 	put_module(NET_BUFFER_MODULE_NAME);
235 	gBufferModule = NULL;
236 	return B_OK;
237 }
238 
239 
240 status_t
241 dialup_up(net_device* _device)
242 {
243 	dialup_device* device = (dialup_device*)_device;
244 
245 	device->fd = open(device->name, O_RDWR);
246 	if (device->fd < 0)
247 		return errno;
248 
249 	device->media = IFM_ACTIVE;
250 
251 	// init port
252 	if (ioctl(device->fd, TCGETA, &device->line_config,
253 		sizeof(device->line_config)) < 0)
254 		goto err;
255 
256 	// adjust options
257 	device->line_config.c_cflag &= ~CBAUD;
258 	device->line_config.c_cflag &= CSIZE;
259 	device->line_config.c_cflag &= CS8;
260 	device->line_config.c_cflag |= B115200;	// TODO: make this configurable too...
261 	device->line_config.c_cflag |= (CLOCAL | CREAD);
262 	device->line_config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
263 	device->line_config.c_oflag &= ~OPOST;
264 	device->line_config.c_cc[VMIN] = 0;
265 	device->line_config.c_cc[VTIME] = 10;
266 
267 	// set new options
268 	if(ioctl(device->fd, TCSETA, &device->line_config,
269 		sizeof(device->line_config)) < 0)
270 		goto err;
271 
272 	// init modem & start dialing phase
273 
274 	char reply[32];
275 
276 	if (strlen(device->init_string) > 0) {
277 		// Send modem init string
278 		if (send_command(device, device->init_string) != B_OK
279 			|| read_command_reply(device, device->init_string,
280 				reply, sizeof(reply)) != B_OK
281 			|| strcmp(reply, "OK")) {
282 			errno = B_IO_ERROR;
283 			goto err;
284 		}
285 	}
286 
287 	if (strlen(device->dial_string) > 0) {
288 		// Send dialing string
289 		device->state = DIALING;
290 		if (send_command(device, device->dial_string) != B_OK
291 			|| read_command_reply(device, device->dial_string,
292 				reply, sizeof(reply)) != B_OK
293 			|| strncmp(reply, "CONNECT", 7)) {
294 			errno = B_IO_ERROR;
295 			goto err;
296 		}
297 	}
298 
299 	device->state = UP;
300 	device->data_mode = true;
301 
302 	device->media |= IFM_FULL_DUPLEX;
303 	device->flags |= IFF_LINK;
304 
305 	device->link_quality = 1000;
306 	if (strlen(reply) > 7) {
307 		// get speed from "CONNECTxxxx" reply
308 		device->link_speed = atoi(&reply[8]);
309 	} else {
310 		// Set default speed (theorically, it could be 300 bits/s even)
311 		device->link_speed = 19200;
312 	}
313 
314 	return B_OK;
315 
316 err:
317 	close(device->fd);
318 	device->fd = -1;
319 	device->media = 0;
320 
321 	return errno;
322 }
323 
324 
325 void
326 dialup_down(net_device* _device)
327 {
328 	dialup_device* device = (dialup_device*)_device;
329 
330 	if (device->flags & IFF_LINK
331 		&& hangup(device) == B_OK)
332 		device->flags &= ~IFF_LINK;
333 
334 	close(device->fd);
335 	device->fd = -1;
336 	device->media = 0;
337 }
338 
339 
340 status_t
341 dialup_control(net_device* _device, int32 op, void* argument,
342 	size_t length)
343 {
344 	dialup_device* device = (dialup_device*)_device;
345 	return ioctl(device->fd, op, argument, length);
346 }
347 
348 
349 status_t
350 dialup_send_data(net_device* _device, net_buffer* buffer)
351 {
352 	dialup_device* device = (dialup_device*)_device;
353 
354 	if (device->fd == -1)
355 		return B_FILE_ERROR;
356 
357 	dprintf("try to send HDLC packet of %lu bytes (flags %ld):\n", buffer->size, buffer->flags);
358 
359 	if (buffer->size < HDLC_HEADER_LENGTH)
360 		return B_BAD_VALUE;
361 
362 	iovec* ioVectors = NULL;
363 	iovec* ioVector;
364 	uint8* packet = NULL;
365 	int packetSize = 0;
366 	status_t status;
367 	ssize_t bytesWritten;
368 	bigtime_t now;
369 
370 	uint32 vectorCount = gBufferModule->count_iovecs(buffer);
371 	if (vectorCount < 1) {
372 		status = B_BAD_VALUE;
373 		goto err;
374 	}
375 
376 	ioVectors = (iovec*)malloc(sizeof(iovec)*vectorCount);
377 	if (ioVectors == NULL) {
378 		status = B_NO_MEMORY;
379 		goto err;
380 	}
381 	gBufferModule->get_iovecs(buffer, ioVectors, vectorCount);
382 
383 	// encode HDLC packet
384 
385 	// worst case: begin and end sequence flags and each byte needing escape
386 	packet = (uint8*)malloc(2 + 2 * buffer->size);
387 	if (packet == NULL) {
388 		status = B_NO_MEMORY;
389 		goto err;
390 	}
391 
392 	// Mark frame start if the prior frame closing flag was sent
393 	// more than a second ago.
394 	// Otherwise, the prior closing flag sequence is the open flag of this
395 	// frame
396 	now = system_time();
397 	if (now - device->last_closing_flag_sequence_time > 1000000)
398 		packet[packetSize++] = HDLC_FLAG_SEQUENCE;
399 
400 	// encode frame data
401 	ioVector = ioVectors;
402 	while (vectorCount--) {
403 		uint8* data = (uint8*) ioVector->iov_base;
404 		for (unsigned int i = 0; i < ioVector->iov_len; i++) {
405 			if (data[i] < 0x20
406 				|| data[i] == HDLC_FLAG_SEQUENCE
407 				|| data[i] == HDLC_CONTROL_ESCAPE) {
408 				// needs escape
409 				packet[packetSize++] = HDLC_CONTROL_ESCAPE;
410 				packet[packetSize++] = data[i] ^ 0x20;
411 			} else
412 				packet[packetSize++] = data[i];
413 		}
414 		// next io vector
415 		ioVector++;
416 	}
417 
418 	// mark frame end
419 	packet[packetSize++] = HDLC_FLAG_SEQUENCE;
420 
421 	// send HDLC packet
422 
423 	bytesWritten = write(device->fd, packet, packetSize);
424 	if (bytesWritten < 0) {
425 		status = errno;
426 		goto err;
427 	}
428 	device->last_closing_flag_sequence_time = system_time();
429 
430 	device->stats.send.packets++;
431 	device->stats.send.bytes += bytesWritten;
432 	status = B_OK;
433 	goto done;
434 
435 err:
436 	device->stats.send.errors++;
437 
438 done:
439 	free(ioVectors);
440 	free(packet);
441 	return status;
442 }
443 
444 
445 status_t
446 dialup_receive_data(net_device* _device, net_buffer** _buffer)
447 {
448 	dialup_device* device = (dialup_device*)_device;
449 
450 	if (device->fd == -1)
451 		return B_FILE_ERROR;
452 
453 	return ENOBUFS;
454 }
455 
456 
457 status_t
458 dialup_set_mtu(net_device* _device, size_t mtu)
459 {
460 	dialup_device* device = (dialup_device*)_device;
461 
462 	device->mtu = mtu;
463 	return B_OK;
464 }
465 
466 
467 status_t
468 dialup_set_promiscuous(net_device* _device, bool promiscuous)
469 {
470 	return B_NOT_SUPPORTED;
471 }
472 
473 
474 status_t
475 dialup_set_media(net_device* device, uint32 media)
476 {
477 	return B_NOT_SUPPORTED;
478 }
479 
480 
481 status_t
482 dialup_add_multicast(struct net_device* _device, const sockaddr* _address)
483 {
484 	return B_NOT_SUPPORTED;
485 }
486 
487 
488 status_t
489 dialup_remove_multicast(struct net_device* _device, const sockaddr* _address)
490 {
491 	return B_NOT_SUPPORTED;
492 }
493 
494 
495 static status_t
496 dialup_std_ops(int32 op, ...)
497 {
498 	switch (op) {
499 		case B_MODULE_INIT:
500 		{
501 			status_t status = get_module(NET_STACK_MODULE_NAME,
502 				(module_info**)&sStackModule);
503 			if (status < B_OK)
504 				return status;
505 
506 			return B_OK;
507 		}
508 
509 		case B_MODULE_UNINIT:
510 		{
511 			put_module(NET_STACK_MODULE_NAME);
512 			return B_OK;
513 		}
514 
515 		default:
516 			return B_ERROR;
517 	}
518 }
519 
520 
521 net_device_module_info sDialUpModule = {
522 	{
523 		"network/devices/dialup/v1",
524 		0,
525 		dialup_std_ops
526 	},
527 	dialup_init,
528 	dialup_uninit,
529 	dialup_up,
530 	dialup_down,
531 	dialup_control,
532 	dialup_send_data,
533 	dialup_receive_data,
534 	dialup_set_mtu,
535 	dialup_set_promiscuous,
536 	dialup_set_media,
537 	dialup_add_multicast,
538 	dialup_remove_multicast,
539 };
540 
541 module_info* modules[] = {
542 	(module_info*)&sDialUpModule,
543 	NULL
544 };
545