// -*- mode: c++; c-basic-offset: 2 -*- /* * rawsocket.{cc,hh} -- transports raw IP packets via safe raw sockets (user-level) * * Mark Huang * * Copyright (c) 2004-2005 The Trustees of Princeton University (Trustees). * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, subject to the conditions * listed in the Click LICENSE file. These conditions include: you must * preserve this copyright notice, and you cannot mention the copyright * holders in advertising related to the Software without their permission. * The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This * notice is a summary of the Click LICENSE file; the license in that file is * legally binding. */ #include #include "rawsocket.hh" #include #include #include #include #include #include #include #include #include #include #include #ifndef __sun #include #else #include #endif #include "fakepcap.hh" CLICK_DECLS RawSocket::RawSocket() : _task(this), _timer(this), _fd(-1), _port(0), _snaplen(2048), _headroom(Packet::DEFAULT_HEADROOM), _rq(0), _wq(0) { } RawSocket::~RawSocket() { } int RawSocket::configure(Vector &conf, ErrorHandler *errh) { // remove keyword arguments if (cp_va_parse_remove_keywords(conf, 1, this, errh, "SNAPLEN", cpUnsigned, "maximum packet length", &_snaplen, "HEADROOM", cpUnsigned, "how much header to allocate for the packet", &_headroom, cpEnd) < 0) return -1; String socktype; if (cp_va_parse(conf, this, errh, cpString, "type of socket (`TCP', `UDP', `GRE', `ICMP')", &socktype, cpIgnoreRest, cpEnd) < 0) return -1; socktype = socktype.upper(); // binding a port is optional if (conf.size() > 1) { if (cp_va_parse(conf, this, errh, cpIgnore, cpUnsignedShort, "port number", &_port, cpIgnoreRest, cpEnd) < 0) return -1; } if (socktype == "TCP") _protocol = IPPROTO_TCP; else if (socktype == "UDP") _protocol = IPPROTO_UDP; else if (socktype == "GRE") _protocol = IPPROTO_GRE; else if (socktype == "ICMP") _protocol = IPPROTO_ICMP; else return errh->error("unknown socket type `%s'", socktype.c_str()); return 0; } int RawSocket::initialize_socket_error(ErrorHandler *errh, const char *syscall) { int e = errno; // preserve errno if (_fd >= 0) { close(_fd); _fd = -1; } return errh->error("%s: %s", syscall, strerror(e)); } int RawSocket::initialize(ErrorHandler *errh) { struct sockaddr_in sin; // open socket, set options, bind to address _fd = socket(PF_INET, SOCK_RAW, _protocol); if (_fd < 0) return initialize_socket_error(errh, "socket"); if (_port) { memset(&sin, 0, sizeof(sin)); sin.sin_family = PF_INET; sin.sin_port = htons(_port); sin.sin_addr = inet_makeaddr(0, 0); // bind to port if (bind(_fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) return initialize_socket_error(errh, "bind"); click_chatter("%s(%d, %d)\n", declaration().c_str(), _protocol, _port); } // nonblocking I/O and close-on-exec for the socket fcntl(_fd, F_SETFL, O_NONBLOCK); fcntl(_fd, F_SETFD, FD_CLOEXEC); // include IP header int one = 1; if (setsockopt(_fd, 0, IP_HDRINCL, &one, sizeof(one)) < 0) return initialize_socket_error(errh, "IP_HDRINCL"); if (noutputs()) add_select(_fd, SELECT_READ); if (ninputs()) { ScheduleInfo::join_scheduler(this, &_task, errh); _signal = Notifier::upstream_empty_signal(this, 0, &_task); } return 0; } void RawSocket::cleanup(CleanupStage) { if (_rq) _rq->kill(); if (_wq) _wq->kill(); if (_fd >= 0) { close(_fd); remove_select(_fd, SELECT_READ | SELECT_WRITE); _fd = -1; } } void RawSocket::selected(int fd) { ErrorHandler *errh = ErrorHandler::default_handler(); int len; if (noutputs()) { // read data from socket if (!_rq) _rq = Packet::make(_headroom, (const unsigned char *)0, _snaplen, 0); if (_rq) { len = recv(_fd, _rq->data(), _rq->length(), MSG_TRUNC); if (len > 0) { if (len > _snaplen) { assert(_rq->length() == (uint32_t)_snaplen); SET_EXTRA_LENGTH_ANNO(_rq, len - _snaplen); } else _rq->take(_snaplen - len); // set timestamp (void) ioctl(fd, SIOCGSTAMP, &_rq->timestamp_anno()); // set IP annotations if (fake_pcap_force_ip(_rq, FAKE_DLT_RAW)) output(0).push(_rq); else _rq->kill(); _rq = 0; } else { if (len == 0 || errno != EAGAIN) { if (errno != EAGAIN) errh->error("recv: %s", strerror(errno)); } } } } if (ninputs()) { // write data to socket Packet *p; if (_wq) { p = _wq; _wq = 0; } else { p = input(0).pull(); } if (p) { // cast to int so very large plen is interpreted as negative if ((int)p->length() < (int)sizeof(click_ip)) { errh->error("runt IP packet (%d bytes)", p->length()); p->kill(); } else { const click_ip *ip = (const click_ip *) p->data(); struct sockaddr_in sin; // set up destination memset(&sin, 0, sizeof(sin)); sin.sin_family = PF_INET; sin.sin_addr = ip->ip_dst; while (p->length()) { // send packet len = sendto(_fd, p->data(), p->length(), 0, (const struct sockaddr*)&sin, sizeof(sin)); if (len < 0) { if (errno == ENOBUFS || errno == EAGAIN) { // socket queue full, try again later _wq = p; remove_select(_fd, SELECT_WRITE); _events &= ~SELECT_WRITE; _backoff = (!_backoff) ? 1 : _backoff*2; _timer.schedule_after(Timestamp::make_usec(_backoff)); return; } else if (errno == EINTR) { // interrupted by signal, try again immediately continue; } else { // unexpected error: drop packet errh->error("sendto: %s", strerror(errno)); break; } } else { p->pull(len); } } _backoff = 0; p->kill(); } } // nothing to write, wait for upstream signal if (!p && !_signal && (_events & SELECT_WRITE)) { remove_select(_fd, SELECT_WRITE); _events &= ~SELECT_WRITE; } } } void RawSocket::run_timer(Timer *) { if ((_wq || _signal) && !(_events & SELECT_WRITE) && _fd >= 0) { add_select(_fd, SELECT_WRITE); _events |= SELECT_WRITE; selected(_fd); } } bool RawSocket::run_task() { if ((_wq || _signal) && !(_events & SELECT_WRITE) && _fd >= 0) { add_select(_fd, SELECT_WRITE); _events |= SELECT_WRITE; selected(_fd); return true; } return false; } void RawSocket::add_handlers() { add_task_handlers(&_task); } CLICK_ENDDECLS ELEMENT_REQUIRES(userlevel linux) EXPORT_ELEMENT(RawSocket)