| // Copyright 2023 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/check.h" |
| #include "secagentd/bpf/bpf_types.h" |
| #include "secagentd/bpf_skeleton_wrappers.h" |
| #include "secagentd/bpf_skeletons/skeleton_file_bpf.h" |
| #include "secagentd/common.h" |
| |
| namespace secagentd { |
| NetworkBpfSkeleton::NetworkBpfSkeleton( |
| uint32_t batch_interval_s, |
| std::unique_ptr<shill::Client> shill, |
| std::optional<SkeletonCallbacks<network_bpf>> cbs) |
| : batch_interval_s_(batch_interval_s), weak_ptr_factory_(this) { |
| CHECK(shill != nullptr); |
| platform_ = GetPlatform(); |
| SkeletonCallbacks<network_bpf> skel_cbs; |
| if (cbs) { |
| skel_cbs = std::move(*cbs); |
| } else { |
| skel_cbs.destroy = base::BindRepeating(network_bpf__destroy); |
| skel_cbs.open = base::BindRepeating(network_bpf__open); |
| skel_cbs.open_opts = base::BindRepeating(network_bpf__open_opts); |
| } |
| shill_ = std::move(shill); |
| default_bpf_skeleton_ = |
| std::make_unique<BpfSkeleton<network_bpf>>("network", skel_cbs); |
| } |
| |
| int NetworkBpfSkeleton::ConsumeEvent() { |
| return default_bpf_skeleton_->ConsumeEvent(); |
| } |
| |
| std::pair<absl::Status, metrics::BpfAttachResult> |
| NetworkBpfSkeleton::LoadAndAttach() { |
| shill_->RegisterOnAvailableCallback(base::BindOnce( |
| &NetworkBpfSkeleton::OnShillAvailable, weak_ptr_factory_.GetWeakPtr())); |
| auto rv = default_bpf_skeleton_->LoadAndAttach(); |
| if (!rv.first.ok()) { |
| return rv; |
| } |
| scan_bpf_maps_timer_.Start( |
| FROM_HERE, base::Seconds(batch_interval_s_), |
| base::BindRepeating(&NetworkBpfSkeleton::ScanFlowMap, |
| weak_ptr_factory_.GetWeakPtr())); |
| return rv; |
| } |
| |
| void NetworkBpfSkeleton::OnShillProcessChanged(bool is_reset) { |
| if (is_reset) { |
| LOG(INFO) << "Shill was reset."; |
| return; |
| } |
| LOG(INFO) << "Shill was shutdown."; |
| shill_->RegisterOnAvailableCallback(base::BindOnce( |
| &NetworkBpfSkeleton::OnShillAvailable, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NetworkBpfSkeleton::OnShillAvailable(bool success) { |
| if (!success) { |
| LOG(ERROR) << __func__ << "Shill not actually ready."; |
| // TODO(b:277815178): Add a UMA metric to log errors related to external |
| // interface fetching. |
| return; |
| } |
| brillo::VariantDictionary properties; |
| brillo::ErrorPtr error; |
| LOG(INFO) << "Shill is now available."; |
| shill_->RegisterProcessChangedHandler( |
| base::BindRepeating(&NetworkBpfSkeleton::OnShillProcessChanged, |
| weak_ptr_factory_.GetWeakPtr())); |
| shill_->RegisterDeviceAddedHandler(base::BindRepeating( |
| &NetworkBpfSkeleton::OnShillDeviceAdded, weak_ptr_factory_.GetWeakPtr())); |
| shill_->RegisterDeviceRemovedHandler( |
| base::BindRepeating(&NetworkBpfSkeleton::OnShillDeviceRemoved, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void NetworkBpfSkeleton::AddExternalDevice( |
| const shill::Client::Device* device) { |
| auto map = |
| default_bpf_skeleton_->skel_->maps.cros_network_external_interfaces; |
| int64_t key = platform_->IfNameToIndex(device->ifname.c_str()); |
| int64_t value = key; |
| if (key == 0) { |
| LOG(ERROR) << "Network: Could not determine ifindex for ifname " |
| << device->ifname; |
| return; |
| } |
| int rv = platform_->BpfMapUpdateElem(map, &key, sizeof(key), &value, |
| sizeof(value), BPF_NOEXIST); |
| if (rv == EEXIST) { |
| LOG(WARNING) << "Network: External device " << device->ifname |
| << " idx: " << value |
| << " already in the BPF external device map."; |
| } else if (rv < 0) { |
| LOG(ERROR) << "Network: Unable to add " << device->ifname |
| << " idx: " << value << " to the BPF external device map."; |
| // TODO(b:277815178): Add a UMA metric to log errors related to external |
| // interface fetching. |
| } else { |
| LOG(INFO) << "ifname: " << device->ifname << " ifindex: " << key |
| << " added to external device map."; |
| } |
| } |
| |
| void NetworkBpfSkeleton::RemoveExternalDevice( |
| const shill::Client::Device* device) { |
| auto map = |
| default_bpf_skeleton_->skel_->maps.cros_network_external_interfaces; |
| int64_t key = platform_->IfNameToIndex(device->ifname.c_str()); |
| if (platform_->BpfMapDeleteElem(map, &key, sizeof(key), 0) < 0) { |
| LOG(ERROR) << "Failed to remove " << device->ifname |
| << " from BPF external device map."; |
| // TODO(b:277815178): Add a UMA metric to log errors related to external |
| // interface fetching. |
| } else { |
| VLOG(1) << device->ifname << ":" << key |
| << " removed from external device map."; |
| } |
| } |
| |
| void NetworkBpfSkeleton::OnShillDeviceAdded( |
| const shill::Client::Device* const device) { |
| // Called when a new device is added (even if it's a VPN device) |
| // Other virtual, non-external devices are not added. |
| AddExternalDevice(device); |
| } |
| |
| void NetworkBpfSkeleton::OnShillDeviceRemoved( |
| const shill::Client::Device* const device) { |
| RemoveExternalDevice(device); |
| } |
| |
| std::unordered_set<uint64_t> NetworkBpfSkeleton::GetActiveSocketsSet() { |
| uint64_t* cur_key{nullptr}; |
| uint64_t next_key{0}; |
| int bpf_rv{0}; |
| std::unordered_set<uint64_t> rv; |
| auto& socket_map = default_bpf_skeleton_->skel_->maps.active_socket_map; |
| do { |
| bpf_rv = platform_->BpfMapGetNextKey(socket_map, cur_key, &next_key, |
| sizeof(next_key)); |
| cur_key = &next_key; |
| if (bpf_rv == 0 || bpf_rv == -ENOENT) { // ENOENT means last key |
| rv.insert(next_key); |
| } |
| } while (bpf_rv == 0); |
| return rv; |
| } |
| |
| void NetworkBpfSkeleton::ScanFlowMap() { |
| /* iterate through the entire map generating one synthetic event |
| * per entry. This is relatively cheap as this is basically a function call. |
| * no IPC message passing is actually being done. |
| */ |
| int rv = 0; |
| auto& skel_maps = default_bpf_skeleton_->skel_->maps; |
| auto& skel_flow_map = skel_maps.cros_network_flow_map; |
| std::unordered_set<uint64_t> active_sockets; |
| |
| // Build a set of currently active sockets. |
| // This will let us determine which network flows can be cleaned up. |
| active_sockets = GetActiveSocketsSet(); |
| bpf::cros_flow_map_key* cur_key = nullptr; |
| bpf::cros_flow_map_key* next_key = nullptr; |
| std::vector<bpf::cros_flow_map_key> flow_map_entries_to_delete; |
| bpf::cros_event cros_event; |
| next_key = &cros_event.data.network_event.data.flow.flow_map_key; |
| auto& network_event = cros_event.data.network_event; |
| auto& event_flow = network_event.data.flow; |
| auto& event_flow_map_value = event_flow.flow_map_value; |
| network_event.type = bpf::kSyntheticNetworkFlow; |
| cros_event.type = bpf::kNetworkEvent; |
| uint64_t loop_count = 0; |
| constexpr uint64_t max_loop{CROS_MAX_SOCKET * CROS_AVG_CONN_PER_SOCKET * 2}; |
| do { |
| loop_count++; |
| rv = platform_->BpfMapGetNextKey( |
| default_bpf_skeleton_->skel_->maps.cros_network_flow_map, cur_key, |
| next_key, sizeof(*next_key)); |
| cur_key = next_key; |
| if (rv == 0 || rv == -ENOENT) { // ENOENT means last key. |
| if (platform_->BpfMapLookupElem(skel_flow_map, cur_key, sizeof(*cur_key), |
| &event_flow_map_value, |
| sizeof(event_flow_map_value), 0) < 0) { |
| LOG(ERROR) << "Flow metrics map retrieval failed for a given key."; |
| // TODO(b:277815178): Add a UMA metric to log errors. |
| continue; |
| } |
| if (active_sockets.find(event_flow_map_value.sock_id) == |
| active_sockets.end()) { |
| // Delay the deletion of flow map events. It may not be safe to |
| // delete elements while we're iterating through the map. |
| flow_map_entries_to_delete.push_back(*cur_key); |
| } |
| default_bpf_skeleton_->callbacks_.ring_buffer_event_callback.Run( |
| cros_event); |
| } |
| if (loop_count > max_loop) { |
| break; |
| } |
| } while (rv == 0); |
| // Garbage collect entries in the flow map. |
| for (const auto& flow_key : flow_map_entries_to_delete) { |
| platform_->BpfMapDeleteElem(skel_flow_map, &flow_key, sizeof(flow_key), 0); |
| } |
| } |
| |
| void NetworkBpfSkeleton::RegisterCallbacks(BpfCallbacks cbs) { |
| default_bpf_skeleton_->RegisterCallbacks(std::move(cbs)); |
| } |
| |
| absl::StatusOr<int> NetworkBpfSkeleton::FindBpfMapByName( |
| const std::string& name) { |
| return default_bpf_skeleton_->FindBpfMapByName(name); |
| } |
| |
| FileBpfSkeleton::FileBpfSkeleton(std::optional<SkeletonCallbacks<file_bpf>> cbs) |
| : weak_ptr_factory_(this) { |
| platform_ = GetPlatform(); |
| SkeletonCallbacks<file_bpf> skel_cbs; |
| if (cbs) { |
| skel_cbs = std::move(*cbs); |
| } else { |
| skel_cbs.destroy = base::BindRepeating(file_bpf__destroy); |
| skel_cbs.open = base::BindRepeating(file_bpf__open); |
| skel_cbs.open_opts = base::BindRepeating(file_bpf__open_opts); |
| } |
| default_bpf_skeleton_ = |
| std::make_unique<BpfSkeleton<file_bpf>>("file", skel_cbs); |
| } |
| |
| int FileBpfSkeleton::ConsumeEvent() { |
| return default_bpf_skeleton_->ConsumeEvent(); |
| } |
| |
| std::pair<absl::Status, metrics::BpfAttachResult> |
| FileBpfSkeleton::LoadAndAttach() { |
| return default_bpf_skeleton_->LoadAndAttach(); |
| } |
| |
| void FileBpfSkeleton::RegisterCallbacks(BpfCallbacks cbs) { |
| default_bpf_skeleton_->RegisterCallbacks(std::move(cbs)); |
| } |
| absl::StatusOr<int> FileBpfSkeleton::FindBpfMapByName(const std::string& name) { |
| return default_bpf_skeleton_->FindBpfMapByName(name); |
| } |
| } // namespace secagentd |