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