| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/if_tun.h> |
| #include <linux/types.h> |
| #include <math.h> |
| #include <net/if.h> |
| #include <netinet/in.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include <deque> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/at_exit.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/files/file_descriptor_watcher_posix.h" |
| #include "base/logging.h" |
| #include "base/rand_util.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/threading/thread.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_tick_clock.h" |
| #include "media/cast/test/utility/udp_proxy.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/udp/udp_socket.h" |
| #include "starboard/memory.h" |
| #include "starboard/string.h" |
| #include "starboard/types.h" |
| |
| namespace cobalt { |
| namespace media { |
| namespace cast { |
| namespace test { |
| |
| const size_t kMaxPacketSize = 4096; |
| |
| class SendToFDPipe : public PacketPipe { |
| public: |
| explicit SendToFDPipe(int fd) : fd_(fd) {} |
| void Send(std::unique_ptr<Packet> packet) final { |
| while (1) { |
| int written = |
| write(fd_, reinterpret_cast<char*>(&packet->front()), packet->size()); |
| if (written < 0) { |
| if (errno == EINTR) continue; |
| perror("write"); |
| exit(1); |
| } |
| if (written != static_cast<int>(packet->size())) { |
| fprintf(stderr, "Truncated write!\n"); |
| exit(1); |
| } |
| break; |
| } |
| } |
| |
| private: |
| int fd_; |
| }; |
| |
| class QueueManager { |
| public: |
| QueueManager(int input_fd, int output_fd, std::unique_ptr<PacketPipe> pipe) |
| : input_fd_(input_fd), packet_pipe_(std::move(pipe)) { |
| read_socket_watch_controller_ = base::FileDescriptorWatcher::WatchReadable( |
| input_fd_, base::Bind(&QueueManager::OnFileCanReadWithoutBlocking, |
| base::Unretained(this))); |
| |
| std::unique_ptr<PacketPipe> tmp(new SendToFDPipe(output_fd)); |
| if (packet_pipe_) { |
| packet_pipe_->AppendToPipe(std::move(tmp)); |
| } else { |
| packet_pipe_ = std::move(tmp); |
| } |
| packet_pipe_->InitOnIOThread(base::ThreadTaskRunnerHandle::Get(), |
| &tick_clock_); |
| } |
| |
| private: |
| void OnFileCanReadWithoutBlocking() { |
| std::unique_ptr<Packet> packet(new Packet(kMaxPacketSize)); |
| int nread = read(input_fd_, reinterpret_cast<char*>(&packet->front()), |
| kMaxPacketSize); |
| if (nread < 0) { |
| if (errno == EINTR) return; |
| perror("read"); |
| exit(1); |
| } |
| if (nread == 0) return; |
| packet->resize(nread); |
| packet_pipe_->Send(std::move(packet)); |
| } |
| |
| int input_fd_; |
| std::unique_ptr<PacketPipe> packet_pipe_; |
| std::unique_ptr<base::FileDescriptorWatcher::Controller> |
| read_socket_watch_controller_; |
| base::DefaultTickClock tick_clock_; |
| }; |
| |
| } // namespace test |
| } // namespace cast |
| } // namespace media |
| } // namespace cobalt |
| |
| base::TimeTicks last_printout; |
| |
| class ByteCounter { |
| public: |
| ByteCounter() : bytes_(0), packets_(0) { push(base::TimeTicks::Now()); } |
| |
| base::TimeDelta time_range() { |
| return time_data_.back() - time_data_.front(); |
| } |
| |
| void push(base::TimeTicks now) { |
| byte_data_.push_back(bytes_); |
| packet_data_.push_back(packets_); |
| time_data_.push_back(now); |
| while (time_range().InSeconds() > 10) { |
| byte_data_.pop_front(); |
| packet_data_.pop_front(); |
| time_data_.pop_front(); |
| } |
| } |
| |
| double megabits_per_second() { |
| double megabits = (byte_data_.back() - byte_data_.front()) * 8 / 1E6; |
| return megabits / time_range().InSecondsF(); |
| } |
| |
| double packets_per_second() { |
| double packets = packet_data_.back() - packet_data_.front(); |
| return packets / time_range().InSecondsF(); |
| } |
| |
| void Increment(uint64_t x) { |
| bytes_ += x; |
| packets_++; |
| } |
| |
| private: |
| uint64_t bytes_; |
| uint64_t packets_; |
| std::deque<uint64_t> byte_data_; |
| std::deque<uint64_t> packet_data_; |
| std::deque<base::TimeTicks> time_data_; |
| }; |
| |
| ByteCounter in_pipe_input_counter; |
| ByteCounter in_pipe_output_counter; |
| ByteCounter out_pipe_input_counter; |
| ByteCounter out_pipe_output_counter; |
| |
| class ByteCounterPipe : public media::cast::test::PacketPipe { |
| public: |
| explicit ByteCounterPipe(ByteCounter* counter) : counter_(counter) {} |
| void Send(std::unique_ptr<media::cast::Packet> packet) final { |
| counter_->Increment(packet->size()); |
| pipe_->Send(std::move(packet)); |
| } |
| |
| private: |
| ByteCounter* counter_; |
| }; |
| |
| void SetupByteCounters(std::unique_ptr<media::cast::test::PacketPipe>* pipe, |
| ByteCounter* pipe_input_counter, |
| ByteCounter* pipe_output_counter) { |
| media::cast::test::PacketPipe* new_pipe = |
| new ByteCounterPipe(pipe_input_counter); |
| new_pipe->AppendToPipe(std::move(*pipe)); |
| new_pipe->AppendToPipe(std::unique_ptr<media::cast::test::PacketPipe>( |
| new ByteCounterPipe(pipe_output_counter))); |
| pipe->reset(new_pipe); |
| } |
| |
| void CheckByteCounters() { |
| base::TimeTicks now = base::TimeTicks::Now(); |
| in_pipe_input_counter.push(now); |
| in_pipe_output_counter.push(now); |
| out_pipe_input_counter.push(now); |
| out_pipe_output_counter.push(now); |
| if ((now - last_printout).InSeconds() >= 5) { |
| fprintf(stderr, "Sending : %5.2f / %5.2f mbps %6.2f / %6.2f packets / s\n", |
| in_pipe_output_counter.megabits_per_second(), |
| in_pipe_input_counter.megabits_per_second(), |
| in_pipe_output_counter.packets_per_second(), |
| in_pipe_input_counter.packets_per_second()); |
| fprintf(stderr, "Receiving: %5.2f / %5.2f mbps %6.2f / %6.2f packets / s\n", |
| out_pipe_output_counter.megabits_per_second(), |
| out_pipe_input_counter.megabits_per_second(), |
| out_pipe_output_counter.packets_per_second(), |
| out_pipe_input_counter.packets_per_second()); |
| |
| last_printout = now; |
| } |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, base::Bind(&CheckByteCounters), |
| base::TimeDelta::FromMilliseconds(100)); |
| } |
| |
| int tun_alloc(char* dev, int flags) { |
| struct ifreq ifr; |
| int fd, err; |
| const char* clonedev = "/dev/net/tun"; |
| |
| /* Arguments taken by the function: |
| * |
| * char *dev: the name of an interface (or '\0'). MUST have enough |
| * space to hold the interface name if '\0' is passed |
| * int flags: interface flags (eg, IFF_TUN etc.) |
| */ |
| |
| /* open the clone device */ |
| if ((fd = open(clonedev, O_RDWR)) < 0) { |
| return fd; |
| } |
| |
| /* preparation of the struct ifr, of type "struct ifreq" */ |
| SbMemorySet(&ifr, 0, sizeof(ifr)); |
| |
| ifr.ifr_flags = flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */ |
| |
| if (*dev) { |
| /* if a device name was specified, put it in the structure; otherwise, |
| * the kernel will try to allocate the "next" device of the |
| * specified type */ |
| SbStringCopy(ifr.ifr_name, dev, IFNAMSIZ); |
| } |
| |
| /* try to create the device */ |
| if ((err = ioctl(fd, TUNSETIFF, &ifr)) < 0) { |
| close(fd); |
| return err; |
| } |
| |
| if (!*dev) { |
| /* if the operation was successful, write back the name of the |
| * interface to the variable "dev", so the caller can know |
| * it. Note that the caller MUST reserve space in *dev (see calling |
| * code below) */ |
| SbStringCopyUnsafe(dev, ifr.ifr_name); |
| } |
| |
| /* this is the special file descriptor that the caller will use to talk |
| * with the virtual interface */ |
| return fd; |
| } |
| |
| int main(int argc, char** argv) { |
| base::AtExitManager exit_manager; |
| base::CommandLine::Init(argc, argv); |
| InitLogging(logging::LoggingSettings()); |
| |
| if (argc < 4) { |
| fprintf(stderr, "Usage: tap_proxy tap1 tap2 type\n"); |
| fprintf(stderr, |
| "Where 'type' is one of perfect, good, wifi, bad or evil\n"); |
| exit(1); |
| } |
| |
| std::unique_ptr<media::cast::test::PacketPipe> in_pipe, out_pipe; |
| std::string network_type = argv[3]; |
| if (network_type == "perfect") { |
| // No action needed. |
| } else if (network_type == "good") { |
| in_pipe = media::cast::test::GoodNetwork(); |
| out_pipe = media::cast::test::GoodNetwork(); |
| } else if (network_type == "wifi") { |
| in_pipe = media::cast::test::WifiNetwork(); |
| out_pipe = media::cast::test::WifiNetwork(); |
| } else if (network_type == "bad") { |
| in_pipe = media::cast::test::BadNetwork(); |
| out_pipe = media::cast::test::BadNetwork(); |
| } else if (network_type == "evil") { |
| in_pipe = media::cast::test::EvilNetwork(); |
| out_pipe = media::cast::test::EvilNetwork(); |
| } else { |
| fprintf(stderr, "Unknown network type.\n"); |
| exit(1); |
| } |
| |
| SetupByteCounters(&in_pipe, &in_pipe_input_counter, &in_pipe_output_counter); |
| SetupByteCounters(&out_pipe, &out_pipe_input_counter, |
| &out_pipe_output_counter); |
| |
| int fd1 = tun_alloc(argv[1], IFF_TAP); |
| int fd2 = tun_alloc(argv[2], IFF_TAP); |
| |
| base::MessageLoopForIO message_loop; |
| base::FileDescriptorWatcher file_descriptor_watcher(&message_loop); |
| last_printout = base::TimeTicks::Now(); |
| media::cast::test::QueueManager qm1(fd1, fd2, std::move(in_pipe)); |
| media::cast::test::QueueManager qm2(fd2, fd1, std::move(out_pipe)); |
| CheckByteCounters(); |
| printf("Press Ctrl-C when done.\n"); |
| base::RunLoop().Run(); |
| } |