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