Newer
Older
XinYang_IOS / Pods / OpenVPNAdapter / Sources / OpenVPN3 / openvpn / dco / ovpndcocli.hpp
@zhangfeng zhangfeng on 7 Dec 2023 11 KB 1.8.0
//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2020 OpenVPN Inc.
//    Copyright (C) 2020-2020 Lev Stipakov <lev@openvpn.net>
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

// tun/transport client for ovpn-dco

class OvpnDcoClient : public Client, public KoRekey::Receiver {
  friend class ClientConfig;
  friend class GeNL;

  typedef RCPtr<OvpnDcoClient> Ptr;
  typedef GeNL<OvpnDcoClient *> GeNLImpl;

  struct PacketFrom {
    typedef std::unique_ptr<PacketFrom> SPtr;
    BufferAllocated buf;
  };

public:
  virtual void tun_start(const OptionList &opt, TransportClient &transcli,
                         CryptoDCSettings &dc_settings) override {
    // notify parent
    tun_parent->tun_pre_tun_config();

    // parse pushed options
    TunBuilderCapture::Ptr po;
    TunBuilderBase *builder;

    if (config->builder) {
      builder = config->builder;
    } else {
      po.reset(new TunBuilderCapture());
      builder = po.get();
    }

    TunProp::configure_builder(
        builder, state.get(), config->transport.stats.get(),
        server_endpoint_addr(), config->tun.tun_prop, opt, nullptr, false);

    if (po)
      OPENVPN_LOG("CAPTURED OPTIONS:" << std::endl << po->to_string());

    if (config->builder) {
      config->builder->tun_builder_dco_establish();
    } else {
      ActionList::Ptr add_cmds = new ActionList();
      remove_cmds.reset(new ActionListReversed());

      std::vector<IP::Route> rtvec;

      TUN_LINUX::tun_config(config->dev_name, *po, &rtvec, *add_cmds,
                            *remove_cmds, true);

      // execute commands to bring up interface
      add_cmds->execute_log();
    }

    // Add a hook so ProtoContext will call back to
    // rekey() on rekey ops.
    dc_settings.set_factory(CryptoDCFactory::Ptr(new KoRekey::Factory(
        dc_settings.factory(), this, config->transport.frame)));

    // signal that we are connected
    tun_parent->tun_connected();
  }

  virtual std::string tun_name() const override { return "ovpn-dco"; }

  virtual void transport_start() override { transport_start_udp(); }

  virtual bool transport_send_const(const Buffer &buf) override {
    return send(buf);
  }

  virtual bool transport_send(BufferAllocated &buf) override {
    return send(buf);
  }

  bool send(const Buffer &buf) {
    if (config->builder) {
      pipe->write_some(buf.const_buffer());
    } else {
      genl->send_data(buf.c_data(), buf.size());
    }
    return true;
  }

  virtual void start_impl_udp(const openvpn_io::error_code &error) override {
    if (halt)
      return;

    if (!error) {
      auto &sock = udp().socket;
      auto local = sock.local_endpoint();
      auto remote = sock.remote_endpoint();

      TunBuilderBase *tb = config->builder;
      if (tb) {
        tb->tun_builder_new();
        // pipe fd which is used to communicate to kernel
        int fd =
            tb->tun_builder_dco_enable(sock.native_handle(), config->dev_name);
        if (fd == -1) {
          stop_();
          transport_parent->transport_error(Error::TUN_IFACE_CREATE,
                                            "error creating ovpn-dco device");
          return;
        }
        pipe.reset(new openvpn_io::posix::stream_descriptor(io_context, fd));
        tb->tun_builder_dco_new_peer(local.address().to_string(), local.port(),
                                     remote.address().to_string(),
                                     remote.port());

        queue_read_pipe(nullptr);

        transport_parent->transport_connecting();
      } else {
        std::ostringstream os;
        int res = TunNetlink::iface_new(os, config->dev_name, "ovpn-dco");
        if (res != 0) {
          stop_();
          transport_parent->transport_error(Error::TUN_IFACE_CREATE, os.str());
        } else {
          genl.reset(new GeNLImpl(
              io_context, if_nametoindex(config->dev_name.c_str()), this));

          genl->start_vpn(sock.native_handle());
          genl->new_peer(local, remote);

          transport_parent->transport_connecting();
        }
      }
    } else {
      std::ostringstream os;
      os << "UDP connect error on '" << server_host << ':' << server_port
         << "' (" << udp().server_endpoint << "): " << error.message();
      config->transport.stats->error(Error::UDP_CONNECT_ERROR);
      stop_();
      transport_parent->transport_error(Error::UNDEF, os.str());
    }
  }

  virtual void stop_() override {
    if (!halt) {
      halt = true;

      if (config->builder) {
        config->builder->tun_builder_teardown(true);
        if (pipe)
          pipe->close();
      } else {
        std::ostringstream os;
        if (genl)
          genl->stop();
        int res = TunNetlink::iface_del(os, config->dev_name);
        if (res != 0) {
          OPENVPN_LOG("ovpndcocli: error deleting iface ovpn:" << os.str());
        }
      }
    }
  }

  virtual void rekey(const CryptoDCInstance::RekeyType rktype,
                     const KoRekey::Info &rkinfo) override {
    if (halt)
      return;

    if (config->builder)
      rekey_impl_tb(rktype, rkinfo);
    else
      rekey_impl(rktype, rkinfo);
  }

  void rekey_impl(const CryptoDCInstance::RekeyType rktype,
                  const KoRekey::Info &rkinfo) {
    KoRekey::OvpnDcoKey key(rktype, rkinfo);
    auto kc = key();

    switch (rktype) {
    case CryptoDCInstance::ACTIVATE_PRIMARY:
      genl->new_key(OVPN_KEY_SLOT_PRIMARY, kc);

      handle_keepalive();
      break;

    case CryptoDCInstance::NEW_SECONDARY:
      genl->new_key(OVPN_KEY_SLOT_SECONDARY, kc);
      break;

    case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
      genl->swap_keys();
      break;

    case CryptoDCInstance::DEACTIVATE_SECONDARY:
      genl->del_key(OVPN_KEY_SLOT_SECONDARY);
      break;

    case CryptoDCInstance::DEACTIVATE_ALL:
      // TODO: deactivate all keys
      OPENVPN_LOG("ovpndcocli: deactivate all keys");
      break;

    default:
      OPENVPN_LOG("ovpndcocli: unknown rekey type: " << rktype);
      break;
    }
  }

  void rekey_impl_tb(const CryptoDCInstance::RekeyType rktype,
                     const KoRekey::Info &rkinfo) {
    KoRekey::OvpnDcoKey key(rktype, rkinfo);
    auto kc = key();

    TunBuilderBase *tb = config->builder;

    switch (rktype) {
    case CryptoDCInstance::ACTIVATE_PRIMARY:
      tb->tun_builder_dco_new_key(OVPN_KEY_SLOT_PRIMARY, kc);

      handle_keepalive();
      break;

    case CryptoDCInstance::NEW_SECONDARY:
      tb->tun_builder_dco_new_key(OVPN_KEY_SLOT_SECONDARY, kc);
      break;

    case CryptoDCInstance::PRIMARY_SECONDARY_SWAP:
      tb->tun_builder_dco_swap_keys();
      break;

    case CryptoDCInstance::DEACTIVATE_SECONDARY:
      tb->tun_builder_dco_del_key(OVPN_KEY_SLOT_SECONDARY);
      break;

    case CryptoDCInstance::DEACTIVATE_ALL:
      // TODO: deactivate all keys
      OPENVPN_LOG("ovpndcocli: deactivate all keys");
      break;

    default:
      OPENVPN_LOG("ovpndcocli: unknown rekey type: " << rktype);
      break;
    }
  }

  bool tun_read_handler(BufferAllocated &buf) {
    if (halt)
      return false;

    int8_t cmd = -1;
    buf.read(&cmd, sizeof(cmd));

    switch (cmd) {
    case OVPN_CMD_PACKET:
      transport_parent->transport_recv(buf);
      break;

    case OVPN_CMD_DEL_PEER: {
      stop_();
      int8_t reason = -1;
      buf.read(&reason, sizeof(reason));
      switch (reason) {
      case OVPN_DEL_PEER_REASON_EXPIRED:
        transport_parent->transport_error(Error::TRANSPORT_ERROR,
                                          "keepalive timeout");
        break;

      default:
        std::ostringstream os;
        os << "peer deleted, reason " << reason;
        transport_parent->transport_error(Error::TUN_HALT, os.str());
        break;
      }
      break;
    }

    case -1:
      // consider all errors as fatal
      stop_();
      transport_parent->transport_error(Error::TUN_HALT, buf_to_string(buf));
      return false;
      break;

    default:
      OPENVPN_LOG("Unknown ovpn-dco cmd " << cmd);
      break;
    }

    return true;
  }

private:
  OvpnDcoClient(openvpn_io::io_context &io_context_arg,
                ClientConfig *config_arg, TransportClientParent *parent_arg)
      : Client(io_context_arg, config_arg, parent_arg) {}

  void handle_keepalive() {
    // since userspace doesn't know anything about presense or
    // absense of data channel traffic, ping should be handled in kernel
    if (transport_parent->is_keepalive_enabled()) {
      unsigned int keepalive_interval = 0;
      unsigned int keepalive_timeout = 0;

      // In addition to disabling userspace keepalive,
      // this call also assigns keepalive values to provided arguments
      // default keepalive values could be overwritten by config values,
      // which in turn could be overwritten by pushed options
      transport_parent->disable_keepalive(keepalive_interval,
                                          keepalive_timeout);

      // Allow overide of keepalive timeout
      if (config->ping_restart_override)
        keepalive_timeout = config->ping_restart_override;

      if (config->builder) {
        config->builder->tun_builder_dco_set_peer(keepalive_interval,
                                                  keepalive_timeout);
      } else {
        // enable keepalive in kernel
        genl->set_peer(keepalive_interval, keepalive_timeout);
      }
    }
  }

  void queue_read_pipe(PacketFrom *pkt) {
    if (!pkt) {
      pkt = new PacketFrom();
    }
    // good enough values for control channel packets
    pkt->buf.reset(512, 3072,
                   BufferAllocated::GROW | BufferAllocated::CONSTRUCT_ZERO |
                       BufferAllocated::DESTRUCT_ZERO);
    pipe->async_read_some(
        pkt->buf.mutable_buffer(),
        [self = Ptr(this),
         pkt = PacketFrom::SPtr(pkt)](const openvpn_io::error_code &error,
                                      const size_t bytes_recvd) mutable {
          if (!error) {
            pkt->buf.set_size(bytes_recvd);
            if (self->tun_read_handler(pkt->buf))
              self->queue_read_pipe(pkt.release());
          } else {
            if (!self->halt) {
              OPENVPN_LOG("ovpn-dco pipe read error: " << error.message());
              self->stop_();
              self->transport_parent->transport_error(Error::TUN_HALT,
                                                      error.message());
            }
          }
        });
  }

  // used to communicate to kernel via privileged process
  std::unique_ptr<openvpn_io::posix::stream_descriptor> pipe;

  GeNLImpl::Ptr genl;
};