diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtil.java b/service/src/main/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtil.java
new file mode 100644
index 000000000..1dadb2ec6
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtil.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.util;
+
+import org.apache.commons.lang3.StringUtils;
+import java.util.Optional;
+
+/**
+ * Tools for working with chains of IP addresses in forwarding lists in HTTP headers.
+ *
+ * @see X-Forwarded-For - HTTP | MDN
+ */
+public class ForwardedIpUtil {
+
+ /**
+ * Returns the most recent proxy in a chain described by an {@code X-Forwarded-For} header.
+ *
+ * @param forwardedFor the value of an X-Forwarded-For header
+ *
+ * @return the IP address of the most recent proxy in the forwarding chain, or empty if none was found or
+ * {@code forwardedFor} was null
+ */
+ public static Optional getMostRecentProxy(final String forwardedFor) {
+ return Optional.ofNullable(forwardedFor)
+ .filter(StringUtils::isNotBlank)
+ .map(proxies -> proxies.split(","))
+ .map(proxyArray -> proxyArray[proxyArray.length - 1])
+ .map(String::trim);
+ }
+}
diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtilTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtilTest.java
new file mode 100644
index 000000000..6c5a8a71f
--- /dev/null
+++ b/service/src/test/java/org/whispersystems/textsecuregcm/util/ForwardedIpUtilTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2021 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ForwardedIpUtilTest {
+
+ @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+ @ParameterizedTest
+ @MethodSource("argumentsForGetMostRecentProxy")
+ void getMostRecentProxy(final String forwardedFor, final Optional expectedMostRecentProxy) {
+ assertEquals(expectedMostRecentProxy, ForwardedIpUtil.getMostRecentProxy(forwardedFor));
+ }
+
+ private static Stream argumentsForGetMostRecentProxy() {
+ return Stream.of(
+ arguments(null, Optional.empty()),
+ arguments("", Optional.empty()),
+ arguments(" ", Optional.empty()),
+ arguments("203.0.113.195", Optional.of("203.0.113.195")),
+ arguments("203.0.113.195, 70.41.3.18, 150.172.238.178", Optional.of("150.172.238.178"))
+ );
+ }
+}