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/* \
 "