llarp/vpn/apple.hpp

Namespaces

Name
llarp
[crypto.hpp]
llarp::vpn

Classes

Name
class llarp::vpn::AppleInterface
class llarp::vpn::AppleRouteManager
class llarp::vpn::ApplePlatform

Source code

#pragma once

#include "platform.hpp"
#include "common.hpp"
#include <llarp/vpn/common.hpp>
#include <llarp/util/fd.hpp>
#include <llarp/util/non_blocking.hpp>

#include <sys/kern_control.h>
#include <sys/sys_domain.h>
#include <sys/kern_event.h>

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/uio.h>

#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/if_types.h>
#include <net/route.h>
#include <netinet/if_ether.h>
#include <netinet/in.h>

#include <fcntl.h>
#include <ifaddrs.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <variant>

namespace llarp::vpn
{
  class AppleInterface : public NetworkInterface
  {
    std::unique_ptr<util::FD> m_FD;

    static int
    Exec(const std::string& cmd)
    {
      return system(cmd.c_str());
    }

   public:
    AppleInterface(InterfaceInfo info)
        : NetworkInterface{std::move(info)}
        , m_FD{std::make_unique<util::FD>(::socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL))}
    {
      if (m_FD->fd() == -1)
        throw std::invalid_argument{"cannot open control socket: " + std::string{strerror(errno)}};

      ctl_info cinfo{};
      const std::string apple_utun = "com.apple.net.utun_control";
      std::copy_n(apple_utun.c_str(), apple_utun.size(), cinfo.ctl_name);

      {
        vpn::IOCTL ioc{*m_FD};
        ioc.ioctl(CTLIOCGINFO, &cinfo);
      }
      sockaddr_ctl addr{};
      addr.sc_id = cinfo.ctl_id;

      addr.sc_len = sizeof(addr);
      addr.sc_family = AF_SYSTEM;
      addr.ss_sysaddr = AF_SYS_CONTROL;
      addr.sc_unit = 0;

      if (::connect(m_FD->fd(), (sockaddr*)&addr, sizeof(addr)) < 0)
      {
        m_FD.reset();
        throw std::runtime_error{
            "cannot connect to control socket address: " + std::string{strerror(errno)}};
      }
      uint32_t namesz = IFNAMSIZ;
      std::array<char, IFNAMSIZ + 1> name{};
      if (::getsockopt(m_FD->fd(), SYSPROTO_CONTROL, 2, name.data(), &namesz) < 0)
      {
        m_FD.reset();
        throw std::runtime_error{
            "cannot query for interface name: " + std::string{strerror(errno)}};
      }

      // make underlying file descriptor non blocking
      llarp::util::NonBlocking{*m_FD};

      auto& m_IfName = m_Info.ifname;
      m_IfName = name.data();
      for (const auto& ifaddr : m_Info.addrs)
      {
        if (ifaddr.fam == AF_INET)
        {
          const huint32_t addr = net::TruncateV6(ifaddr.range.addr);
          const huint32_t netmask = net::TruncateV6(ifaddr.range.netmask_bits);
          const huint32_t daddr = addr & netmask;
          Exec(
              "/sbin/ifconfig " + m_IfName + " " + addr.ToString() + " " + daddr.ToString()
              + " mtu 1500 netmask 255.255.255.255 up");
          Exec(
              "/sbin/route add " + daddr.ToString() + " -netmask " + netmask.ToString()
              + " -interface " + m_IfName);
          Exec("/sbin/route add " + addr.ToString() + " -interface lo0");
        }
        else if (ifaddr.fam == AF_INET6)
        {
          Exec("/sbin/ifconfig " + m_IfName + " inet6 " + ifaddr.range.ToString());
        }
      }
    }

    ~AppleInterface() override = default;

    int
    PollFD() const override
    {
      return m_FD->fd();
    }

    net::IPPacket
    ReadNextPacket() override
    {
      constexpr int uintsize = sizeof(unsigned int);
      net::IPPacket pkt{net::IPPacket::MaxSize};

      // Prepare storage for header + max-size packet.
      unsigned int pktinfo = 0;
      std::array<iovec, 2> vecs = {iovec{&pktinfo, uintsize}, iovec{pkt.data(), pkt.size()}};
      int sz = ::readv(m_FD->fd(), vecs.data(), vecs.size());
      if (sz >= uintsize)
      {
        pkt.truncate(sz - uintsize);  // shrink to actual size
      }
      else if (errno == EAGAIN || errno == EWOULDBLOCK)
      {
        pkt.truncate(0);
        errno = 0;
      }
      else
      {
        throw std::error_code{errno, std::system_category()};
      }
      return pkt;
    }

    bool
    WritePacket(net::IPPacket pkt) override
    {
      static unsigned int af4 = htonl(AF_INET);
      static unsigned int af6 = htonl(AF_INET6);
      const void* af_ptr =
          pkt.IsV6() ? static_cast<const void*>(&af6) : static_cast<const void*>(&af4);
      size_t af_len = sizeof(unsigned int);

      std::array<iovec, 2> vecs = {
          iovec{const_cast<void*>(af_ptr), af_len},
          iovec{const_cast<byte_t*>(pkt.data()), pkt.size()}};
      ssize_t n = ::writev(m_FD->fd(), vecs.data(), vecs.size());
      if (n >= static_cast<ssize_t>(af_len))
      {
        n -= af_len;
        return static_cast<size_t>(n) == pkt.size();
      }
      return false;
    }
  };

  class AppleRouteManager : public IRouteManager
  {
   public:
    AppleRouteManager() = default;
    ~AppleRouteManager() override = default;

    // Add a route to a specific IP via a gateway
    void
    AddRoute(net::ipaddr_t ip, net::ipaddr_t gateway) override
    {
      std::string cmd =
          "/sbin/route add -host " + llarp::net::ToString(ip) + " " + llarp::net::ToString(gateway);
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("AddRoute failed: " + cmd);
    }

    void
    DelRoute(net::ipaddr_t ip, net::ipaddr_t gateway) override
    {
      std::string cmd = "/sbin/route delete -host " + llarp::net::ToString(ip) + " "
          + llarp::net::ToString(gateway);
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("DelRoute failed: " + cmd);
    }

    // Add a default route via the VPN interface's first IPv4 address
    void
    AddDefaultRouteViaInterface(NetworkInterface& vpn) override
    {
      const auto& info = vpn.Info();
      if (info.addrs.empty())
        throw std::runtime_error("No interface addresses found");

      std::string gateway = info.addrs[0].range.addr.ToString();
      std::string cmd = "/sbin/route add default " + gateway;
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("AddDefaultRouteViaInterface failed: " + cmd);
    }

    void
    DelDefaultRouteViaInterface(NetworkInterface& vpn) override
    {
      const auto& info = vpn.Info();
      if (info.addrs.empty())
        throw std::runtime_error("No interface addresses found");

      std::string gateway = info.addrs[0].range.addr.ToString();
      std::string cmd = "/sbin/route delete default " + gateway;
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("DelDefaultRouteViaInterface failed: " + cmd);
    }

    // Add a route for a subnet via the VPN interface
    void
    AddRouteViaInterface(NetworkInterface& vpn, IPRange range) override
    {
      std::string cmd = "/sbin/route add -net " + range.addr.ToString() + " -netmask "
          + range.NetmaskString() + " -interface " + vpn.Info().ifname;
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("AddRouteViaInterface failed: " + cmd);
    }

    void
    DelRouteViaInterface(NetworkInterface& vpn, IPRange range) override
    {
      std::string cmd = "/sbin/route delete -net " + range.addr.ToString() + " -netmask "
          + range.NetmaskString() + " -interface " + vpn.Info().ifname;
      int ret = std::system(cmd.c_str());
      if (ret != 0)
        throw std::runtime_error("DelRouteViaInterface failed: " + cmd);
    }

    std::vector<net::ipaddr_t>
    GetGatewaysNotOnInterface(NetworkInterface&) override
    {
      return {};
    }
  };

  class ApplePlatform : public Platform
  {
    AppleRouteManager _routeManager{};

   public:
    std::shared_ptr<NetworkInterface>
    ObtainInterface(InterfaceInfo info, AbstractRouter*) override
    {
      return std::static_pointer_cast<NetworkInterface>(
          std::make_shared<AppleInterface>(std::move(info)));
    };

    IRouteManager&
    RouteManager() override
    {
      return _routeManager;
    }
  };
}  // namespace llarp::vpn

Updated on 2026-01-10 at 22:49:46 +0000