| // Copyright 2019 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef PATCHPANEL_NDPROXY_H_ |
| #define PATCHPANEL_NDPROXY_H_ |
| |
| #include <stdint.h> |
| |
| #include <linux/if_ether.h> |
| #include <linux/in6.h> |
| #include <net/ethernet.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/ip.h> |
| #include <netinet/ip6.h> |
| |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/scoped_file.h> |
| #include <brillo/daemons/daemon.h> |
| #include <gtest/gtest_prod.h> // for FRIEND_TEST |
| |
| #include "patchpanel/mac_address_generator.h" |
| #include "patchpanel/message_dispatcher.h" |
| |
| namespace patchpanel { |
| |
| // Forward ICMPv6 RS/RA/NS/NA mssages between network interfaces according to |
| // RFC 4389. Support asymmetric proxy that RS will be proxied one-way from |
| // guest interface to physical interface ('Outbound') and RA the other way back |
| // ('Inbound'), as well as symmetric proxy among guest interfaces that only |
| // NS/NA will be proxied. |
| class NDProxy { |
| public: |
| static constexpr ssize_t kTranslateErrorNotICMPv6Packet = -1; |
| static constexpr ssize_t kTranslateErrorNotNDPacket = -2; |
| static constexpr ssize_t kTranslateErrorInsufficientLength = -3; |
| static constexpr ssize_t kTranslateErrorBufferMisaligned = -4; |
| static constexpr ssize_t kTranslateErrorMismatchedIp6Length = -5; |
| |
| NDProxy(); |
| NDProxy(const NDProxy&) = delete; |
| NDProxy& operator=(const NDProxy&) = delete; |
| |
| virtual ~NDProxy() = default; |
| |
| // Given the ICMPv6 packet |icmp6| with header and options (payload) of total |
| // byte length |icmp6_len|, returns a pointer to the start of the prefix |
| // information, or returns nullptr if no option of type |
| // ND_OPT_PREFIX_INFORMATION was found. |
| static const nd_opt_prefix_info* GetPrefixInfoOption(const uint8_t* icmp6, |
| size_t icmp6_len); |
| static nd_opt_prefix_info* GetPrefixInfoOption(uint8_t* icmp6, |
| size_t icmp6_len); |
| |
| // Helper function to create a AF_PACKET socket suitable for frame read/write. |
| static base::ScopedFD PreparePacketSocket(); |
| |
| // Initialize the resources needed such as rtnl socket and dummy socket for |
| // ioctl. Return false if failed. |
| bool Init(); |
| |
| // Read one IP packet from AF_PACKET socket |fd| and process it. If proxying |
| // is needed, translated packets are sent out through the same socket. |
| void ReadAndProcessOnePacket(int fd); |
| |
| // NDProxy can trigger a callback upon a neighbor discovered on downlink. This |
| // can be triggered by either receiving a unicast NA, or an NS with non-link |
| // local source address. |
| // Arguments: receiving interface index, neighbor address. |
| void RegisterOnGuestIpDiscoveryHandler( |
| base::RepeatingCallback<void(int, const in6_addr&)> handler); |
| |
| // Callback upon receiving prefix information from RA frame. |
| // Arguments: receiving interface index, prefix address, prefix length. |
| void RegisterOnRouterDiscoveryHandler( |
| base::RepeatingCallback<void(int, const in6_addr&, int)> handler); |
| |
| // Start proxying RS from |if_id_downstream| to |if_id_upstream|, and RA the |
| // other way around. If |modify_router_address| is true we modify source |
| // address when proxying RA so that downstream thinks ChromeOS host as the |
| // router. |
| void StartRSRAProxy(int if_id_upstream, |
| int if_id_downstream, |
| bool modify_router_address = false); |
| // Start proxying NS from |if_id_ns_side| to |if_id_na_side| and NA the other |
| // way around. |
| void StartNSNAProxy(int if_id_na_side, int if_id_ns_side); |
| // Stop all proxying between |if_id1| and |if_id2|. |
| void StopProxy(int if_id1, int if_id2); |
| |
| protected: |
| // RFC 4389: Read the input ICMPv6 packet in |in_packet| and determine whether |
| // it should be proxied. If so, fill the |out_packet| buffer with proxied |
| // packet and return the length of proxied packet (usually same with input |
| // frame length). Return a negative value if proxy is not needed or an error |
| // occurred. |
| // in_packet: buffer containing input IPv6 packet. |
| // packet_len: the length of input IPv6 packet; |
| // local_mac_addr: MAC address of interface that will be used to send the |
| // proxied packet; |
| // new_src_ip: if not null, address that will be used for the IP header |
| // source address to send the proxied packet; |
| // new_dst_ip: if not null, address that will be used for the IP header |
| // destination address to send the proxied packet; |
| // out_packet: buffer for output IPv6 pacet; should have at least |
| // packet_len space. |
| static ssize_t TranslateNDPacket(const uint8_t* in_packet, |
| size_t packet_len, |
| const MacAddress& local_mac_addr, |
| const in6_addr* new_src_ip, |
| const in6_addr* new_dst_ip, |
| uint8_t* out_packet); |
| |
| // Given the ICMPv6 segment |icmp6| with header and options (payload) of total |
| // byte length |icmp6_len|, overwrites in option |opt_type| the mac address |
| // with |target_mac|. |icmp6_len| is the total size in bytes of the ICMPv6 |
| // segment. |nd_hdr_len| is the length of ICMPv6 header (so the first option |
| // starts after |nd_hdr_len|.) |
| static void ReplaceMacInIcmpOption(uint8_t* icmp6, |
| size_t icmp6_len, |
| size_t nd_hdr_len, |
| uint8_t opt_type, |
| const MacAddress& target_mac); |
| |
| // For destination IP address |dest_ipv6|, resolve it into destination MAC |
| // and fill in |dest_mac|. A neighbor table lookup may take place but no NS |
| // message will be sent. If the IP cannot be resolved, all-nodes multicast |
| // address 33:33:00:00:00:01 will be used as a fallback. |
| void ResolveDestinationMac(const in6_addr& dest_ipv6, uint8_t* dest_mac); |
| |
| // Trigger the router discovery and neighbor discovery callbacks upon |
| // receiving a corresponding packet. |
| void NotifyPacketCallbacks(int recv_ifindex, |
| const uint8_t* packet, |
| size_t len); |
| |
| private: |
| // Data structure to store interface mapping for a certain kind of packet to |
| // be proxied. For example, {1: {2}, 2: {1}} means that packet from interfaces |
| // 1 and 2 will be proxied to each other. |
| using interface_mapping = std::map<int, std::set<int>>; |
| |
| // Get MAC address on a local interface through ioctl(). |
| // Returns false upon failure. |
| virtual bool GetLocalMac(int if_id, MacAddress* mac_addr); |
| |
| // Query kernel NDP table and get the MAC address of a certain IPv6 neighbor. |
| // Returns false when neighbor entry is not found. |
| virtual bool GetNeighborMac(const in6_addr& ipv6_addr, MacAddress* mac_addr); |
| |
| // Get the link local IPv6 address on a local interface. |
| // Returns false upon failure. |
| virtual bool GetLinkLocalAddress(int if_id, in6_addr* link_local); |
| |
| interface_mapping* MapForType(uint8_t type); |
| bool IsGuestInterface(int ifindex); |
| bool IsRouterInterface(int ifindex); |
| |
| // Socket used to communicate with kernel through ioctl. No real packet data |
| // goes through this socket. |
| base::ScopedFD dummy_fd_; |
| base::ScopedFD rtnl_fd_; |
| |
| // Fixed buffers for receiving and sending IP packets. |
| uint8_t* in_packet_buffer_[IP_MAXPACKET]; |
| uint8_t* out_packet_buffer_[IP_MAXPACKET]; |
| |
| // Maps of interface names to set of interfaces to which a given ICMP6 types |
| // of ND packet should be forwarded. For any ND packet of a given ICMP6 type |
| // arriving on an interface, the relevant map indicates which other interfaces |
| // this packets should be proxied to. |
| interface_mapping if_map_rs_; |
| interface_mapping if_map_ra_; |
| interface_mapping if_map_ns_; |
| interface_mapping if_map_na_; |
| |
| // The set of uplink interfaces from which the RA should be injected so that |
| // the downstream guests treat the host as the next hop router. |
| std::set<int> modify_ra_uplinks_; |
| |
| // Map from downlink interface id to the link local address on it |
| std::map<int, in6_addr> downlink_link_local_; |
| |
| base::RepeatingCallback<void(int, const in6_addr&)> guest_discovery_handler_; |
| base::RepeatingCallback<void(int, const in6_addr&, int)> |
| router_discovery_handler_; |
| |
| base::WeakPtrFactory<NDProxy> weak_factory_{this}; |
| }; |
| |
| // A wrapper class for running NDProxy in a daemon process. Control messages and |
| // guest IP discovery messages are passed through |control_fd|. |
| class NDProxyDaemon : public brillo::Daemon { |
| public: |
| explicit NDProxyDaemon(base::ScopedFD control_fd); |
| NDProxyDaemon(const NDProxyDaemon&) = delete; |
| NDProxyDaemon& operator=(const NDProxyDaemon&) = delete; |
| |
| virtual ~NDProxyDaemon(); |
| |
| private: |
| // Overrides Daemon init callback. Returns 0 on success and < 0 on error. |
| int OnInit() override; |
| // FileDescriptorWatcher callbacks for new data on fd_. |
| void OnDataSocketReadReady(); |
| // Callbacks to be registered to msg_dispatcher to handle control messages. |
| void OnParentProcessExit(); |
| void OnControlMessage(const SubprocessMessage& msg); |
| |
| // Callback from NDProxy core when receive NA from guest |
| void OnGuestIpDiscovery(int if_id, const in6_addr& ip6addr); |
| |
| // Callback from NDProxy core when receive prefix info from router |
| void OnRouterDiscovery(int if_id, |
| const in6_addr& prefix_addr, |
| int prefix_len); |
| |
| // Utilize MessageDispatcher to watch control fd |
| std::unique_ptr<MessageDispatcher> msg_dispatcher_; |
| |
| // Data fd and its watcher |
| base::ScopedFD fd_; |
| std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher_; |
| |
| NDProxy proxy_; |
| |
| base::WeakPtrFactory<NDProxyDaemon> weak_factory_{this}; |
| }; |
| |
| } // namespace patchpanel |
| |
| #endif // PATCHPANEL_NDPROXY_H_ |