one: Cherry-pick IP Parsing Support
William A. Kennington III (4):
gbmc one: Add support for parsing IPs out of ONE
Start gbmc-one-ips.service before gbmc-br-load-ip.service
gbmc-one-ips: Fix service binary
gbmc_one_ips: Ignore missing one PB
Tested: Integrated the gbmc-one-ips into an image and verified the file
is created properly at bootup from the ONE config.
Fusion-Link: https://fusion2.corp.google.com/ee88fcac-40bb-3902-b451-ea4dd51da594
https://fusion2.corp.google.com/e0dd4666-a5b0-3929-94fc-013f27949891
https://fusion2.corp.google.com/1620a373-3c20-3d35-9899-eb17378a903f
Platforms-Affected: TTF CN BMC
Google-Bug-Id: 423701176
Change-Id: Ia137faca44f686dd3a06071c682a50994f7f3762
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/recipes-google/g3-shared-libs/files/0001-gbmc-one-Add-support-for-parsing-IPs-out-of-ONE.patch b/recipes-google/g3-shared-libs/files/0001-gbmc-one-Add-support-for-parsing-IPs-out-of-ONE.patch
new file mode 100644
index 0000000..73f22d9
--- /dev/null
+++ b/recipes-google/g3-shared-libs/files/0001-gbmc-one-Add-support-for-parsing-IPs-out-of-ONE.patch
@@ -0,0 +1,497 @@
+From c071c00014cfd15284fd14e3d9874185a51ac9d0 Mon Sep 17 00:00:00 2001
+From: "William A. Kennington III" <wak@google.com>
+Date: Mon, 16 Jun 2025 18:40:31 -0700
+Subject: [PATCH 1/4] gbmc one: Add support for parsing IPs out of ONE
+
+We have platforms that have additional fallback addresses for BMC platforms. We want to parse out these addresses and add them to the networking configuration at runtime.
+
+RELNOTES: Add gBMC service for parsing ONE into systemd-networkd .network format
+PiperOrigin-RevId: 772252872
+Change-Id: I6d6142bbe36fdf625df23322eff42a0374f9366d
+---
+ BUILD | 45 +++++++++
+ gbmc-one-ips.service | 6 ++
+ gbmc_one_ips.cc | 82 ++++++++++++++++
+ gbmc_one_ips.h | 40 ++++++++
+ gbmc_one_ips_main.cc | 17 ++++
+ gbmc_one_ips_test.cc | 217 +++++++++++++++++++++++++++++++++++++++++++
+ meson.build | 16 ++++
+ negg9-nfd01.pb | Bin 0 -> 1545 bytes
+ 8 files changed, 423 insertions(+)
+ create mode 100644 subprojects/one/BUILD
+ create mode 100644 subprojects/one/gbmc-one-ips.service
+ create mode 100644 subprojects/one/gbmc_one_ips.cc
+ create mode 100644 subprojects/one/gbmc_one_ips.h
+ create mode 100644 subprojects/one/gbmc_one_ips_main.cc
+ create mode 100644 subprojects/one/gbmc_one_ips_test.cc
+
+diff --git a/BUILD b/BUILD
+new file mode 100644
+index 0000000..8dfc790
+--- /dev/null
++++ b/BUILD
+@@ -0,0 +1,45 @@
++load("//file/memfile:memfile_embed_data.bzl", "memfile_embed_data")
++load("//third_party/bazel_rules/rules_cc/cc:cc_binary.bzl", "cc_binary")
++load("//third_party/bazel_rules/rules_cc/cc:cc_library.bzl", "cc_library")
++
++cc_library(
++ name = "gbmc_one_ips",
++ srcs = ["gbmc_one_ips.cc"],
++ hdrs = ["gbmc_one_ips.h"],
++ deps = [
++ "//production/msv/node_entities/proto:network_interfaces_cc_proto",
++ "//production/msv/node_entities/proto:offline_node_entities_cc_proto",
++ "//production/msv/node_entities/proto:resolved_entities_cc_proto",
++ "//production/msv/node_entities/public:offline_node_entities",
++ "//third_party/absl/status",
++ "//third_party/absl/status:statusor",
++ "//third_party/absl/strings:str_format",
++ ],
++)
++
++cc_binary(
++ name = "gbmc_one_ips_main",
++ srcs = ["gbmc_one_ips_main.cc"],
++ deps = [":gbmc_one_ips"],
++)
++
++memfile_embed_data(
++ name = "gbmc_one_ips_test_files",
++ srcs = ["negg9-nfd01.pb"],
++)
++
++cc_test(
++ name = "gbmc_one_ips_test",
++ srcs = ["gbmc_one_ips_test.cc"],
++ deps = [
++ ":gbmc_one_ips",
++ ":gbmc_one_ips_test_files",
++ "//file/base",
++ "//file/memfile",
++ "//testing/base/public:gunit_main",
++ "//third_party/absl/log:check",
++ "//third_party/absl/status",
++ "//third_party/absl/strings",
++ "//third_party/protobuf",
++ ],
++)
+diff --git a/gbmc-one-ips.service b/gbmc-one-ips.service
+new file mode 100644
+index 0000000..201b9a5
+--- /dev/null
++++ b/gbmc-one-ips.service
+@@ -0,0 +1,6 @@
++[Service]
++Type=oneshot
++ExecStart=/usr/libexec/gbmc_one_ips
++
++[Install]
++WantedBy=multi-user.target
+diff --git a/gbmc_one_ips.cc b/gbmc_one_ips.cc
+new file mode 100644
+index 0000000..1db1251
+--- /dev/null
++++ b/gbmc_one_ips.cc
+@@ -0,0 +1,82 @@
++#include "gbmc_one_ips.h"
++
++#include <filesystem> // NOLINT
++#include <fstream>
++#include <optional>
++#include <string>
++#include <system_error> // NOLINT
++#include <vector>
++
++#include "network_interfaces.pb.h"
++#include "offline_node_entities.pb.h"
++#include "resolved_entities.pb.h"
++#include "public_offline_node_entities.h"
++#include "absl/status/status.h"
++#include "absl/status/statusor.h"
++#include "absl/strings/str_format.h"
++
++namespace platforms {
++namespace gbmc {
++
++absl::StatusOr<std::vector<std::string>> OneToIps(
++ const production_msv::node_entities_proto::OfflineNodeEntityInformation
++ &one) {
++ if (!one.has_resolved_config())
++ return absl::InternalError("Missing resolved config");
++ if (one.entity_tag().empty())
++ return absl::InternalError("Missing entity tag");
++ for (const auto &[name, entity] : one.resolved_config().entities()) {
++ // Only match the node we are running this binary on
++ if (name != one.entity_tag()) {
++ continue;
++ }
++ if (!entity.has_network_interfaces()) {
++ return absl::InternalError(
++ absl::StrFormat("%s missing network interfaces", name));
++ }
++ std::vector<std::string> ret;
++ for (const auto &intf : entity.network_interfaces().network_interface()) {
++ if (!intf.has_ipv6_address()) {
++ continue;
++ }
++ ret.push_back(intf.ipv6_address());
++ }
++ return ret;
++ }
++ return absl::InternalError(absl::StrFormat(
++ "Failed to find entity_tag in one: %s", one.entity_tag()));
++}
++
++absl::Status WriteIpListFile(const char *filename,
++ const std::vector<std::string> &ips) {
++ auto dir = std::filesystem::path(filename).parent_path();
++ std::error_code ec;
++ std::filesystem::create_directories(dir, ec);
++ if (ec) {
++ return absl::InternalError(absl::StrFormat(
++ "Failed to create IP List directory: %s", dir.native()));
++ }
++
++ std::ofstream os(filename);
++ for (const auto &ip : ips) os << ip << "\n";
++ os.close();
++ if (!os.good()) {
++ return absl::InternalError(
++ absl::StrFormat("Failed to write IP List file: %s", filename));
++ }
++ return absl::OkStatus();
++}
++
++absl::Status OneToIpListFile(std::optional<absl::string_view> one_path_override,
++ const char *filename) {
++ auto one_or_err =
++ production_msv::node_entities::ReadOfflineNodeEntityInformation(
++ one_path_override);
++ if (!one_or_err.ok()) return one_or_err.status();
++ auto ips_or_err = OneToIps(one_or_err.value());
++ if (!ips_or_err.ok()) return ips_or_err.status();
++ return WriteIpListFile(filename, ips_or_err.value());
++}
++
++} // namespace gbmc
++} // namespace platforms
+diff --git a/gbmc_one_ips.h b/gbmc_one_ips.h
+new file mode 100644
+index 0000000..953710a
+--- /dev/null
++++ b/gbmc_one_ips.h
+@@ -0,0 +1,40 @@
++#ifndef PLATFORMS_GBMC_G3_SHARED_LIBS_SUBPROJECTS_ONE_GBMC_ONE_IPS_H_
++#define PLATFORMS_GBMC_G3_SHARED_LIBS_SUBPROJECTS_ONE_GBMC_ONE_IPS_H_
++
++#include <string>
++#include <vector>
++
++#include "offline_node_entities.pb.h"
++#include "absl/status/status.h"
++#include "absl/status/statusor.h"
++
++namespace platforms {
++namespace gbmc {
++
++// OneToIPs()
++//
++// Returns a list of all IP addresses for the current BMC node in the provided
++// offline node entity information.
++absl::StatusOr<std::vector<std::string>> OneToIps(
++ const production_msv::node_entities_proto::OfflineNodeEntityInformation
++ &one);
++
++// WriteIpListFile()
++//
++// Write the list of IPs to the file at the given `filename`, each on a separate
++// line.
++absl::Status WriteIpListFile(const char *filename,
++ const std::vector<std::string> &ips);
++
++// OneToIpListFile()
++//
++// A convenience helper combining the above to functions with reading the
++// offline node entity configuration from the default path. This writes the list
++// of IPs to the provided `filename`.
++absl::Status OneToIpListFile(std::optional<absl::string_view> one_path_override,
++ const char *filename);
++
++} // namespace gbmc
++} // namespace platforms
++
++#endif // PLATFORMS_GBMC_G3_SHARED_LIBS_SUBPROJECTS_ONE_GBMC_ONE_IPS_H_
+diff --git a/gbmc_one_ips_main.cc b/gbmc_one_ips_main.cc
+new file mode 100644
+index 0000000..6eb8454
+--- /dev/null
++++ b/gbmc_one_ips_main.cc
+@@ -0,0 +1,17 @@
++#include <iostream>
++#include <optional>
++
++#include "gbmc_one_ips.h"
++
++// Reads the offline node entities file from the BMC system and writes
++// all of the IPs for the current BMC node to the /var/gbmc-br-ips/one
++// file.
++int main() {
++ auto st = platforms::gbmc::OneToIpListFile(
++ /*one_path_override=*/std::nullopt, "/run/gbmc-br-ips/one");
++ if (!st.ok()) {
++ std::cerr << "gbmc-one-ips failed: " << st << "\n" << std::flush;
++ return 1;
++ }
++ return 0;
++}
+diff --git a/gbmc_one_ips_test.cc b/gbmc_one_ips_test.cc
+new file mode 100644
+index 0000000..b0699ca
+--- /dev/null
++++ b/gbmc_one_ips_test.cc
+@@ -0,0 +1,217 @@
++#include "gbmc_one_ips.h"
++
++#include <string>
++
++#include "file/base/filesystem.h"
++#include "file/base/helpers.h"
++#include "file/base/options.h"
++#include "testing/base/public/gmock.h"
++#include "testing/base/public/gunit.h"
++#include "absl/log/check.h"
++#include "absl/status/status.h"
++#include "absl/strings/str_cat.h"
++#include "google/protobuf/text_format.h"
++
++namespace platforms {
++namespace gbmc {
++
++using ONEI = production_msv::node_entities_proto::OfflineNodeEntityInformation;
++
++TEST(OneToIPs, EmptyProto) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ "Missing resolved config"));
++}
++
++TEST(OneToIPs, MissingEntityTag) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(
++ resolved_config {}
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ "Missing entity tag"));
++}
++
++TEST(OneToIPs, MissingEntityInConfig) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(
++ entity_tag: "bmc"
++ resolved_config {}
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one), testing::status::StatusIs(
++ absl::StatusCode::kInternal,
++ "Failed to find entity_tag in one: bmc"));
++}
++
++TEST(OneToIPs, MissingNetworkInterfaces) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(
++ entity_tag: "bmc"
++ resolved_config {
++ entities {
++ key: "host"
++ value: {}
++ }
++ entities {
++ key: "bmc"
++ value: {}
++ }
++ }
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ "bmc missing network interfaces"));
++}
++
++TEST(OneToIPs, NoAddress) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(
++ R"pb(
++ entity_tag: "bmc"
++ resolved_config {
++ entities {
++ key: "host"
++ value: {}
++ }
++ entities {
++ key: "bmc"
++ value: { network_interfaces {} }
++ }
++ }
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one), testing::status::IsOkAndHolds(testing::IsEmpty()));
++}
++
++TEST(OneToIPs, MultiAddress) {
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(
++ R"pb(
++ entity_tag: "bmc"
++ resolved_config {
++ entities {
++ key: "host"
++ value: {
++ network_interfaces {
++ network_interface { ipv6_address: "9::9" }
++ network_interface { ipv6_address: "0::0" }
++ }
++ }
++ }
++ entities {
++ key: "bmc"
++ value: {
++ network_interfaces {
++ network_interface { ipv6_address: "1::2" }
++ network_interface { ipv6_address: "2::3" }
++ }
++ }
++ }
++ }
++ )pb",
++ &one));
++ EXPECT_THAT(OneToIps(one), testing::status::IsOkAndHolds(
++ testing::ElementsAre("1::2", "2::3")));
++}
++
++TEST(OneToIPs, RealConfig) {
++ ONEI one;
++ CHECK_OK(
++ file::GetBinaryProto("/memfile/gbmc_one_ips_test_files/negg9-nfd01.pb",
++ &one, file::Defaults()));
++ EXPECT_THAT(OneToIps(one), testing::status::IsOkAndHolds(testing::ElementsAre(
++ "2002:a05:6861:8089:fd01::")));
++}
++
++TEST(WriteIPListFile, DirectoryFail) {
++ auto dirname = absl::StrCat(testing::TempDir(), "/not-a-dir");
++ ONEI one;
++ CHECK_OK(file::SetContents(dirname, "", file::Defaults()));
++ auto filename = absl::StrCat(dirname, "/file");
++ EXPECT_THAT(
++ WriteIpListFile(filename.c_str(), {}),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ testing::HasSubstr("IP List directory")));
++}
++
++TEST(WriteIPListFile, WriteFail) {
++ auto filename = testing::TempDir();
++ EXPECT_THAT(WriteIpListFile(filename.c_str(), {}),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ testing::HasSubstr("write IP List")));
++}
++
++TEST(WriteIPListFile, Empty) {
++ auto filename = absl::StrCat(testing::TempDir(), "/new-dir/empty-ips");
++ EXPECT_THAT(WriteIpListFile(filename.c_str(), {}), testing::status::IsOk());
++ std::string contents;
++ EXPECT_THAT(file::GetContents(filename, &contents, file::Defaults()),
++ testing::status::IsOk());
++ EXPECT_EQ(contents, "");
++}
++
++TEST(WriteIPListFile, Multi) {
++ auto filename = absl::StrCat(testing::TempDir(), "/multi-ips");
++ EXPECT_THAT(WriteIpListFile(filename.c_str(), {"1::1", "2::2"}),
++ testing::status::IsOk());
++ std::string contents;
++ EXPECT_THAT(file::GetContents(filename, &contents, file::Defaults()),
++ testing::status::IsOk());
++ EXPECT_EQ(contents, "1::1\n2::2\n");
++}
++
++TEST(OneToIPListFile, FailPBRead) {
++ auto pbfile = absl::StrCat(testing::TempDir(), "/no-such-file");
++ EXPECT_THAT(OneToIpListFile(pbfile, ""),
++ testing::status::StatusIs(
++ testing::_, testing::HasSubstr("Failed to open file")));
++}
++
++TEST(OneToIPListFile, FailParsing) {
++ auto pbfile = absl::StrCat(testing::TempDir(), "/bad-one.pb");
++ std::string contents;
++ ONEI one;
++ ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(R"pb(
++ entity_tag: "bmc"
++ )pb",
++ &one));
++ one.SerializeToString(&contents);
++ CHECK_OK(file::SetContents(pbfile, contents, file::Defaults()));
++ EXPECT_THAT(OneToIpListFile(pbfile, ""),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ "Missing resolved config"));
++}
++
++TEST(OneToIPListFile, FailWriting) {
++ auto pbfile = absl::StrCat(testing::TempDir(), "/negg9-nfd01.pb");
++ CHECK_OK(file::Copy("/memfile/gbmc_one_ips_test_files/negg9-nfd01.pb", pbfile,
++ file::Defaults()));
++ auto ipfile = absl::StrCat(pbfile, "/bad-filename");
++ EXPECT_THAT(
++ OneToIpListFile(pbfile, ipfile.c_str()),
++ testing::status::StatusIs(absl::StatusCode::kInternal,
++ testing::HasSubstr("IP List directory")));
++}
++
++TEST(OneToIPListFile, Success) {
++ auto pbfile = absl::StrCat(testing::TempDir(), "/negg9-nfd01.pb2");
++ CHECK_OK(file::Copy("/memfile/gbmc_one_ips_test_files/negg9-nfd01.pb", pbfile,
++ file::Defaults()));
++ auto ipfile = absl::StrCat(testing::TempDir(), "/ip-list");
++ EXPECT_THAT(OneToIpListFile(pbfile, ipfile.c_str()), testing::status::IsOk());
++ std::string contents;
++ EXPECT_THAT(file::GetContents(ipfile, &contents, file::Defaults()),
++ testing::status::IsOk());
++ EXPECT_EQ(contents, "2002:a05:6861:8089:fd01::\n");
++}
++
++} // namespace gbmc
++} // namespace platforms
+diff --git a/meson.build b/meson.build
+index e277566..4c6c609 100644
+--- a/meson.build
++++ b/meson.build
+@@ -77,3 +77,19 @@ import('pkgconfig').generate(
+ version: meson.project_version(),
+ libraries: lib_one,
+ )
++
++executable(
++ 'gbmc-one-ips',
++ 'gbmc_one_ips.cc',
++ 'gbmc_one_ips_main.cc',
++ link_with: lib_one,
++ dependencies: [
++ protobuf_deps,
++ abseil_deps,
++ ],
++ install_dir : get_option('libexecdir'),
++ install: true)
++
++install_data(
++ 'gbmc-one-ips.service',
++ install_dir: get_option('libdir') / 'systemd' / 'system')
+--
+2.50.0.727.gbf7dc18ff4-goog
+
diff --git a/recipes-google/g3-shared-libs/files/0002-Start-gbmc-one-ips.service-before-gbmc-br-load-ip.se.patch b/recipes-google/g3-shared-libs/files/0002-Start-gbmc-one-ips.service-before-gbmc-br-load-ip.se.patch
new file mode 100644
index 0000000..6e777cb
--- /dev/null
+++ b/recipes-google/g3-shared-libs/files/0002-Start-gbmc-one-ips.service-before-gbmc-br-load-ip.se.patch
@@ -0,0 +1,27 @@
+From 1c644ca483c76c7b4ff070f573d5ed15fd13312a Mon Sep 17 00:00:00 2001
+From: "William A. Kennington III" <wak@google.com>
+Date: Tue, 17 Jun 2025 10:36:27 -0700
+Subject: [PATCH 2/4] Start gbmc-one-ips.service before gbmc-br-load-ip.service
+
+We need the list of IPs to be generated before we load them into the bridge
+
+PiperOrigin-RevId: 772531190
+Change-Id: I972375a456228329f00d422f80265f612fcab69e
+---
+ gbmc-one-ips.service | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/gbmc-one-ips.service b/gbmc-one-ips.service
+index 201b9a5..f434a57 100644
+--- a/gbmc-one-ips.service
++++ b/gbmc-one-ips.service
+@@ -1,3 +1,6 @@
++[Unit]
++Before=gbmc-br-load-ip.service
++
+ [Service]
+ Type=oneshot
+ ExecStart=/usr/libexec/gbmc_one_ips
+--
+2.50.0.727.gbf7dc18ff4-goog
+
diff --git a/recipes-google/g3-shared-libs/files/0003-gbmc-one-ips-Fix-service-binary.patch b/recipes-google/g3-shared-libs/files/0003-gbmc-one-ips-Fix-service-binary.patch
new file mode 100644
index 0000000..7cc5fe9
--- /dev/null
+++ b/recipes-google/g3-shared-libs/files/0003-gbmc-one-ips-Fix-service-binary.patch
@@ -0,0 +1,29 @@
+From b227a7fabe843bd330ebcf9e0a9c5e8e9cf0ac08 Mon Sep 17 00:00:00 2001
+From: "William A. Kennington III" <wak@google.com>
+Date: Tue, 17 Jun 2025 14:07:23 -0700
+Subject: [PATCH 3/4] gbmc-one-ips: Fix service binary
+
+The meson file builds gbmc-one-ips, but the service is calling gbmc_one_ips
+
+PiperOrigin-RevId: 772613308
+Change-Id: I43ea8b2b24d76d06c0760b6e6371d4e8f05e21df
+---
+ gbmc-one-ips.service | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/gbmc-one-ips.service b/gbmc-one-ips.service
+index f434a57..6856a8c 100644
+--- a/gbmc-one-ips.service
++++ b/gbmc-one-ips.service
+@@ -3,7 +3,7 @@ Before=gbmc-br-load-ip.service
+
+ [Service]
+ Type=oneshot
+-ExecStart=/usr/libexec/gbmc_one_ips
++ExecStart=/usr/libexec/gbmc-one-ips
+
+ [Install]
+ WantedBy=multi-user.target
+--
+2.50.0.727.gbf7dc18ff4-goog
+
diff --git a/recipes-google/g3-shared-libs/files/0004-gbmc_one_ips-Ignore-missing-one-PB.patch b/recipes-google/g3-shared-libs/files/0004-gbmc_one_ips-Ignore-missing-one-PB.patch
new file mode 100644
index 0000000..17d6041
--- /dev/null
+++ b/recipes-google/g3-shared-libs/files/0004-gbmc_one_ips-Ignore-missing-one-PB.patch
@@ -0,0 +1,87 @@
+From 9e9e255206768c3d987e5f800dc26fe1e76bfd4d Mon Sep 17 00:00:00 2001
+From: "William A. Kennington III" <wak@google.com>
+Date: Tue, 17 Jun 2025 17:02:46 -0700
+Subject: [PATCH 4/4] gbmc_one_ips: Ignore missing one PB
+
+We have machine instances where the PB file is missing and we don't want to cause the system service to report a failure. Log more information about the IPs we parsed and if we have a missing ONE file.
+
+PiperOrigin-RevId: 772679335
+Change-Id: Ic1e2cfaa0f73f6e399e88c2e922ed84cb7c1a332
+---
+ gbmc_one_ips.cc | 10 +++++++++-
+ gbmc_one_ips.h | 2 +-
+ gbmc_one_ips_test.cc | 9 ++++++++-
+ 3 files changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/gbmc_one_ips.cc b/gbmc_one_ips.cc
+index 1db1251..3f9bb04 100644
+--- a/gbmc_one_ips.cc
++++ b/gbmc_one_ips.cc
+@@ -2,6 +2,7 @@
+
+ #include <filesystem> // NOLINT
+ #include <fstream>
++#include <iostream>
+ #include <optional>
+ #include <string>
+ #include <system_error> // NOLINT
+@@ -40,6 +41,7 @@ absl::StatusOr<std::vector<std::string>> OneToIps(
+ continue;
+ }
+ ret.push_back(intf.ipv6_address());
++ std::cerr << "Using IP: " << intf.ipv6_address() << "\n";
+ }
+ return ret;
+ }
+@@ -72,7 +74,13 @@ absl::Status OneToIpListFile(std::optional<absl::string_view> one_path_override,
+ auto one_or_err =
+ production_msv::node_entities::ReadOfflineNodeEntityInformation(
+ one_path_override);
+- if (!one_or_err.ok()) return one_or_err.status();
++ if (!one_or_err.ok()) {
++ if (absl::IsNotFound(one_or_err.status())) {
++ std::cerr << "Ignoring missing ONE: " << one_or_err.status() << "\n";
++ return absl::OkStatus();
++ }
++ return one_or_err.status();
++ }
+ auto ips_or_err = OneToIps(one_or_err.value());
+ if (!ips_or_err.ok()) return ips_or_err.status();
+ return WriteIpListFile(filename, ips_or_err.value());
+diff --git a/gbmc_one_ips.h b/gbmc_one_ips.h
+index 953710a..3994469 100644
+--- a/gbmc_one_ips.h
++++ b/gbmc_one_ips.h
+@@ -30,7 +30,7 @@ absl::Status WriteIpListFile(const char *filename,
+ //
+ // A convenience helper combining the above to functions with reading the
+ // offline node entity configuration from the default path. This writes the list
+-// of IPs to the provided `filename`.
++// of IPs to the provided `filename`. Allows for non-existent node entity files.
+ absl::Status OneToIpListFile(std::optional<absl::string_view> one_path_override,
+ const char *filename);
+
+diff --git a/gbmc_one_ips_test.cc b/gbmc_one_ips_test.cc
+index b0699ca..facb50e 100644
+--- a/gbmc_one_ips_test.cc
++++ b/gbmc_one_ips_test.cc
+@@ -168,8 +168,15 @@ TEST(WriteIPListFile, Multi) {
+ EXPECT_EQ(contents, "1::1\n2::2\n");
+ }
+
+-TEST(OneToIPListFile, FailPBRead) {
++TEST(OneToIPListFile, MissingPb) {
+ auto pbfile = absl::StrCat(testing::TempDir(), "/no-such-file");
++ EXPECT_THAT(OneToIpListFile(pbfile, pbfile.c_str()), testing::status::IsOk());
++ EXPECT_TRUE(absl::IsNotFound(file::Exists(pbfile, file::Defaults())));
++}
++
++TEST(OneToIPListFile, FailPBRead) {
++ auto pbfile = absl::StrCat(testing::TempDir(), "/invalid-file");
++ EXPECT_EQ(0, symlink(pbfile.c_str(), pbfile.c_str()));
+ EXPECT_THAT(OneToIpListFile(pbfile, ""),
+ testing::status::StatusIs(
+ testing::_, testing::HasSubstr("Failed to open file")));
+--
+2.50.0.727.gbf7dc18ff4-goog
+
diff --git a/recipes-google/g3-shared-libs/one_git.bb b/recipes-google/g3-shared-libs/one_git.bb
index 064b291..5a32a43 100644
--- a/recipes-google/g3-shared-libs/one_git.bb
+++ b/recipes-google/g3-shared-libs/one_git.bb
@@ -5,7 +5,18 @@
G3_PROJ="one"
DEPENDS:append = " protobuf protobuf-native abseil-cpp"
+SRC_URI += " \
+ file://0001-gbmc-one-Add-support-for-parsing-IPs-out-of-ONE.patch \
+ file://0002-Start-gbmc-one-ips.service-before-gbmc-br-load-ip.se.patch \
+ file://0003-gbmc-one-ips-Fix-service-binary.patch \
+ file://0004-gbmc_one_ips-Ignore-missing-one-PB.patch \
+"
+
+inherit systemd
+
+SYSTEMD_SERVICE:${PN} += "gbmc-one-ips.service"
FILES:${PN} = " \
+ ${libexecdir} \
${libdir}/libone${SOLIBS} \
${includedir}/one/* \
"