/* * Copyright 2010, Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT License. * * Authors: * Philippe Houdoin, */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HDLC_FLAG_SEQUENCE 0x7e #define HDLC_CONTROL_ESCAPE 0x7d #define HDLC_ALL_STATIONS 0xff #define HDLC_UI 0x03 #define HDLC_HEADER_LENGTH 4 enum dialup_state { DOWN, DIALING, UP, HANGINGUP }; struct dialup_device : net_device { int fd; struct termios line_config; dialup_state state; bigtime_t last_closing_flag_sequence_time; bool data_mode; char init_string[64]; char dial_string[64]; char escape_string[8]; bigtime_t escape_silence; char hangup_string[16]; bigtime_t tx_flag_timeout; uint32 rx_accm; uint32 tx_accm[8]; }; net_buffer_module_info* gBufferModule; static net_stack_module_info* sStackModule; // #pragma mark - static status_t switch_to_command_mode(dialup_device* device) { if (device->state != UP) return B_ERROR; if (!device->data_mode) return B_OK; snooze(device->escape_silence); ssize_t size = write(device->fd, device->escape_string, strlen(device->escape_string)); if (size != (ssize_t)strlen(device->escape_string)) return B_IO_ERROR; snooze(device->escape_silence); device->data_mode = false; return B_OK; } #if 0 static status_t switch_to_data_mode(dialup_device* device) { if (device->state != UP) return B_ERROR; if (device->data_mode) return B_OK; // TODO: check if it's needed, as these days any // escaped AT commands switch back to data mode automatically // after their completion... ssize_t size = write(device->fd, "ATO", 3); if (size != 3) return B_IO_ERROR; device->data_mode = true; return B_OK; } #endif static status_t send_command(dialup_device* device, const char* command) { status_t status; if (device->data_mode) { status = switch_to_command_mode(device); if (status != B_OK) return status; } ssize_t bytesWritten = write(device->fd, command, strlen(command)); if (bytesWritten != (ssize_t)strlen(command)) return B_IO_ERROR; if (write(device->fd, "\r", 1) != 1) return B_IO_ERROR; return B_OK; } static status_t read_command_reply(dialup_device* device, const char* command, char* reply, int replyMaxSize) { if (device->data_mode) return B_ERROR; int i = 0; while (i < replyMaxSize) { ssize_t bytesRead = read(device->fd, &reply[i], 1); if (bytesRead != 1) return B_IO_ERROR; if (reply[i] == '\n') { // filter linefeed char continue; } if (reply[i] == '\r') { reply[i] = '\0'; // is command reply or command echo (if any) ? if (!strcasecmp(reply, command)) return B_OK; // It's command echo line. Just ignore it. i = 0; continue; } i++; } // replyMaxSize not large enough to store the full reply line. return B_NO_MEMORY; } static status_t hangup(dialup_device* device) { if (device->state != UP) return B_ERROR; // TODO: turn device's DTR down instead. Or do that too after sending command char reply[8]; if (send_command(device, device->hangup_string) != B_OK || read_command_reply(device, device->hangup_string, reply, sizeof(reply)) != B_OK || strcmp(reply, "OK")) return B_ERROR; device->state = DOWN; return B_OK; } // #pragma mark - status_t dialup_init(const char* name, net_device** _device) { // make sure this is a device in /dev/ports if (strncmp(name, "/dev/ports/", 11)) return B_BAD_VALUE; status_t status = get_module(NET_BUFFER_MODULE_NAME, (module_info**)&gBufferModule); if (status < B_OK) return status; dialup_device* device = new (std::nothrow)dialup_device; if (device == NULL) { put_module(NET_BUFFER_MODULE_NAME); return B_NO_MEMORY; } memset(device, 0, sizeof(dialup_device)); strcpy(device->name, name); device->flags = IFF_POINTOPOINT; device->type = IFT_PPP; // this device handle RFC 1331 frame format only device->mtu = 1500; device->media = 0; device->header_length = HDLC_HEADER_LENGTH; device->fd = -1; device->state = DOWN; device->data_mode = false; device->last_closing_flag_sequence_time = 0; // default AT strings strncpy(device->init_string, "ATZ", sizeof(device->init_string)); strncpy(device->dial_string, "ATDT", sizeof(device->dial_string)); strncpy(device->hangup_string, "ATH0", sizeof(device->hangup_string)); strncpy(device->escape_string, "+++", sizeof(device->escape_string)); device->escape_silence = 1000000; device->tx_flag_timeout = 1000000; // default rx & tx Async-Control-Character-Map memset(&device->rx_accm, 0xFF, sizeof(device->rx_accm)); memset(&device->tx_accm, 0xFF, sizeof(device->tx_accm)); *_device = device; return B_OK; } status_t dialup_uninit(net_device* _device) { dialup_device* device = (dialup_device*)_device; delete device; put_module(NET_BUFFER_MODULE_NAME); gBufferModule = NULL; return B_OK; } status_t dialup_up(net_device* _device) { dialup_device* device = (dialup_device*)_device; device->fd = open(device->name, O_RDWR); if (device->fd < 0) return errno; device->media = IFM_ACTIVE; // init port if (ioctl(device->fd, TCGETA, &device->line_config, sizeof(device->line_config)) < 0) goto err; // adjust options device->line_config.c_cflag &= ~CBAUD; device->line_config.c_cflag &= CSIZE; device->line_config.c_cflag &= CS8; device->line_config.c_cflag |= B115200; // TODO: make this configurable too... device->line_config.c_cflag |= (CLOCAL | CREAD); device->line_config.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); device->line_config.c_oflag &= ~OPOST; device->line_config.c_cc[VMIN] = 0; device->line_config.c_cc[VTIME] = 10; // set new options if(ioctl(device->fd, TCSETA, &device->line_config, sizeof(device->line_config)) < 0) goto err; // init modem & start dialing phase char reply[32]; if (strlen(device->init_string) > 0) { // Send modem init string if (send_command(device, device->init_string) != B_OK || read_command_reply(device, device->init_string, reply, sizeof(reply)) != B_OK || strcmp(reply, "OK")) { errno = B_IO_ERROR; goto err; } } reply[0] = '\0'; if (strlen(device->dial_string) > 0) { // Send dialing string device->state = DIALING; if (send_command(device, device->dial_string) != B_OK || read_command_reply(device, device->dial_string, reply, sizeof(reply)) != B_OK || strncmp(reply, "CONNECT", 7)) { errno = B_IO_ERROR; goto err; } } device->state = UP; device->data_mode = true; device->media |= IFM_FULL_DUPLEX; device->flags |= IFF_LINK; device->link_quality = 1000; if (strlen(reply) > 7) { // get speed from "CONNECTxxxx" reply device->link_speed = atoi(&reply[8]); } else { // Set default speed (theorically, it could be 300 bits/s even) device->link_speed = 19200; } return B_OK; err: close(device->fd); device->fd = -1; device->media = 0; return errno; } void dialup_down(net_device* _device) { dialup_device* device = (dialup_device*)_device; if (device->flags & IFF_LINK && hangup(device) == B_OK) device->flags &= ~IFF_LINK; close(device->fd); device->fd = -1; device->media = 0; } status_t dialup_control(net_device* _device, int32 op, void* argument, size_t length) { dialup_device* device = (dialup_device*)_device; return ioctl(device->fd, op, argument, length); } status_t dialup_send_data(net_device* _device, net_buffer* buffer) { dialup_device* device = (dialup_device*)_device; if (device->fd == -1) return B_FILE_ERROR; dprintf("try to send HDLC packet of %" B_PRIu32 " bytes " "(flags 0x%" B_PRIx32 "):\n", buffer->size, buffer->flags); if (buffer->size < HDLC_HEADER_LENGTH) return B_BAD_VALUE; iovec* ioVectors = NULL; iovec* ioVector; uint8* packet = NULL; int packetSize = 0; status_t status; ssize_t bytesWritten; uint32 vectorCount = gBufferModule->count_iovecs(buffer); if (vectorCount < 1) { status = B_BAD_VALUE; goto err; } ioVectors = (iovec*)malloc(sizeof(iovec)*vectorCount); if (ioVectors == NULL) { status = B_NO_MEMORY; goto err; } gBufferModule->get_iovecs(buffer, ioVectors, vectorCount); // encode HDLC packet // worst case: begin and end sequence flags plus each payload byte escaped packet = (uint8*)malloc(2 + 2 * buffer->size); if (packet == NULL) { status = B_NO_MEMORY; goto err; } // Mark frame start if the prior frame closing flag was sent // more than a second ago. // Otherwise, the prior closing flag sequence is the open flag of this // frame if (device->tx_flag_timeout && system_time() - device->last_closing_flag_sequence_time > device->tx_flag_timeout) { packet[packetSize++] = HDLC_FLAG_SEQUENCE; } // encode frame data ioVector = ioVectors; while (vectorCount--) { uint8* data = (uint8*) ioVector->iov_base; for (unsigned int i = 0; i < ioVector->iov_len; i++) { if (data[i] < 0x20 || data[i] == HDLC_FLAG_SEQUENCE || data[i] == HDLC_CONTROL_ESCAPE) { // needs escape packet[packetSize++] = HDLC_CONTROL_ESCAPE; packet[packetSize++] = data[i] ^ 0x20; } else packet[packetSize++] = data[i]; } // next io vector ioVector++; } // mark frame end packet[packetSize++] = HDLC_FLAG_SEQUENCE; // send HDLC packet bytesWritten = write(device->fd, packet, packetSize); if (bytesWritten < 0) { status = errno; goto err; } device->last_closing_flag_sequence_time = system_time(); status = B_OK; goto done; err: done: free(ioVectors); free(packet); return status; } status_t dialup_receive_data(net_device* _device, net_buffer** _buffer) { dialup_device* device = (dialup_device*)_device; if (device->fd == -1) return B_FILE_ERROR; net_buffer* buffer = gBufferModule->create(256); if (buffer == NULL) return ENOBUFS; status_t status; ssize_t bytesRead; uint8* data = NULL; uint8* packet = (uint8*)malloc(2 + 2 * buffer->size); if (packet == NULL) { status = B_NO_MEMORY; goto err; } status = gBufferModule->append_size(buffer, device->mtu + HDLC_HEADER_LENGTH, (void**)&data); if (status == B_OK && data == NULL) { dprintf("scattered I/O is not yet supported by dialup device.\n"); status = B_NOT_SUPPORTED; } if (status < B_OK) goto err; while (true) { bytesRead = read(device->fd, data, device->mtu + HDLC_HEADER_LENGTH); if (bytesRead < 0) { // TODO } } status = gBufferModule->trim(buffer, bytesRead); if (status < B_OK) { atomic_add((int32*)&device->stats.receive.dropped, 1); goto err; } *_buffer = buffer; status = B_OK; goto done; err: gBufferModule->free(buffer); done: free(packet); return status; } status_t dialup_set_mtu(net_device* _device, size_t mtu) { dialup_device* device = (dialup_device*)_device; device->mtu = mtu; return B_OK; } status_t dialup_set_promiscuous(net_device* _device, bool promiscuous) { return B_NOT_SUPPORTED; } status_t dialup_set_media(net_device* device, uint32 media) { return B_NOT_SUPPORTED; } status_t dialup_add_multicast(struct net_device* _device, const sockaddr* _address) { return B_NOT_SUPPORTED; } status_t dialup_remove_multicast(struct net_device* _device, const sockaddr* _address) { return B_NOT_SUPPORTED; } static status_t dialup_std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: { status_t status = get_module(NET_STACK_MODULE_NAME, (module_info**)&sStackModule); if (status < B_OK) return status; return B_OK; } case B_MODULE_UNINIT: { put_module(NET_STACK_MODULE_NAME); return B_OK; } default: return B_ERROR; } } net_device_module_info sDialUpModule = { { "network/devices/dialup/v1", 0, dialup_std_ops }, dialup_init, dialup_uninit, dialup_up, dialup_down, dialup_control, dialup_send_data, dialup_receive_data, dialup_set_mtu, dialup_set_promiscuous, dialup_set_media, dialup_add_multicast, dialup_remove_multicast, }; module_info* modules[] = { (module_info*)&sDialUpModule, NULL };