diff options
author | Matteo Mara <matteo.mara@sonarsource.com> | 2024-10-24 18:00:17 +0200 |
---|---|---|
committer | Lukasz Jarocki <lukasz.jarocki@sonarsource.com> | 2025-02-28 09:57:46 +0100 |
commit | 87a57454f6726dab664945304d21ea6935093e29 (patch) | |
tree | 5fce34ee10b3f83f358cb8a442253dcdc33379e2 /server | |
parent | 9d4672c454b808d6fa5496c57e5cc7d3bb5bdb25 (diff) | |
download | sonarqube-87a57454f6726dab664945304d21ea6935093e29.tar.gz sonarqube-87a57454f6726dab664945304d21ea6935093e29.zip |
SONAR-23454 Add telemetry data about usage of IPv6
Diffstat (limited to 'server')
6 files changed, 223 insertions, 18 deletions
diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java index e1bb4f91938..056c8978407 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java +++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java @@ -20,6 +20,7 @@ package org.sonar.process; import java.net.Inet4Address; +import java.net.Inet6Address; import java.net.InetAddress; import java.util.Optional; import java.util.OptionalInt; @@ -51,6 +52,8 @@ public interface NetworkUtils { boolean isLoopback(String hostOrAddress); + boolean isIpv6Address(String hostOrAddress); + /** * Returns the machine {@link InetAddress} that matches the specified * predicate. If multiple addresses match then a single one @@ -66,4 +69,13 @@ public interface NetworkUtils { default Optional<InetAddress> getLocalNonLoopbackIpv4Address() { return getLocalInetAddress(a -> !a.isLoopbackAddress() && a instanceof Inet4Address); } + + /** + * Returns a local {@link InetAddress} that is IPv6 and not + * loopback. If multiple addresses match then a single one + * is picked in a non deterministic way. + */ + default Optional<InetAddress> getLocalNonLoopbackIpv6Address() { + return getLocalInetAddress(a -> !a.isLoopbackAddress() && a instanceof Inet6Address); + } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java index 6bc1237d9de..d40a63969ac 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java +++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtilsImpl.java @@ -22,6 +22,7 @@ package org.sonar.process; import com.google.common.annotations.VisibleForTesting; import com.google.common.net.InetAddresses; import java.io.IOException; +import java.net.Inet6Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.ServerSocket; @@ -102,7 +103,7 @@ public class NetworkUtilsImpl implements NetworkUtils { return InetAddress.getLocalHost().getHostName(); } catch (UnknownHostException e) { LOG.trace("Failed to get hostname", e); - return "unresolved hostname"; + return "unresolved hostname"; } } @@ -145,6 +146,17 @@ public class NetworkUtilsImpl implements NetworkUtils { } @Override + public boolean isIpv6Address(String hostOrAddress) { + Optional<InetAddress> inetAddress = toInetAddress(hostOrAddress); + boolean ipv6Preferred = Boolean.parseBoolean(System.getProperty("java.net.preferIPv6Addresses")); + + if (inetAddress.isEmpty() || inetAddress.get().isLoopbackAddress()) { + return ipv6Preferred; + } + return inetAddress.get() instanceof Inet6Address; + } + + @Override public Optional<InetAddress> getLocalInetAddress(Predicate<InetAddress> predicate) { try { return Collections.list(NetworkInterface.getNetworkInterfaces()).stream() diff --git a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java index 639fc720ff8..f68604a5cfc 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsImplTest.java @@ -27,19 +27,20 @@ import java.util.HashSet; import java.util.Optional; import java.util.Set; import org.hamcrest.CoreMatchers; -import org.junit.Test; +import org.junit.jupiter.api.Test; import static org.apache.commons.lang3.RandomStringUtils.secure; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assume.assumeThat; -public class NetworkUtilsImplTest { +class NetworkUtilsImplTest { + public static final String PREFER_IPV_6_ADDRESSES = "java.net.preferIPv6Addresses"; - private NetworkUtilsImpl underTest = new NetworkUtilsImpl(); + private final NetworkUtilsImpl underTest = new NetworkUtilsImpl(); @Test - public void getNextAvailablePort_returns_a_port() throws Exception { + void getNextAvailablePort_returns_a_port() throws Exception { String localhost = InetAddress.getLocalHost().getHostName(); int port = underTest.getNextAvailablePort(localhost).getAsInt(); assertThat(port) @@ -48,7 +49,7 @@ public class NetworkUtilsImplTest { } @Test - public void getNextAvailablePort_does_not_return_twice_the_same_port() throws Exception { + void getNextAvailablePort_does_not_return_twice_the_same_port() throws Exception { String localhost = InetAddress.getLocalHost().getHostName(); Set<Integer> ports = new HashSet<>(Arrays.asList( underTest.getNextAvailablePort(localhost).getAsInt(), @@ -58,7 +59,7 @@ public class NetworkUtilsImplTest { } @Test - public void getLocalNonLoopbackIpv4Address_returns_a_valid_local_and_non_loopback_ipv4() { + void getLocalNonLoopbackIpv4Address_returns_a_valid_local_and_non_loopback_ipv4() { Optional<InetAddress> address = underTest.getLocalNonLoopbackIpv4Address(); // address is empty on offline builds @@ -69,7 +70,7 @@ public class NetworkUtilsImplTest { } @Test - public void getHostname_returns_hostname_of_localhost_otherwise_a_constant() { + void getHostname_returns_hostname_of_localhost_otherwise_a_constant() { try { InetAddress localHost = InetAddress.getLocalHost(); assertThat(underTest.getHostname()).isEqualTo(localHost.getHostName()); @@ -80,41 +81,41 @@ public class NetworkUtilsImplTest { } @Test - public void getLocalInetAddress_filters_local_addresses() { + void getLocalInetAddress_filters_local_addresses() { InetAddress address = underTest.getLocalInetAddress(InetAddress::isLoopbackAddress).get(); assertThat(address.isLoopbackAddress()).isTrue(); } @Test - public void getLocalInetAddress_returns_empty_if_no_local_addresses_match() { + void getLocalInetAddress_returns_empty_if_no_local_addresses_match() { Optional<InetAddress> address = underTest.getLocalInetAddress(a -> false); assertThat(address).isEmpty(); } @Test - public void toInetAddress_supports_host_names() { + void toInetAddress_supports_host_names() { assertThat(underTest.toInetAddress("localhost")).isNotEmpty(); // do not test values that require DNS calls. Build must support offline mode. } @Test - public void toInetAddress_supports_ipv4() { + void toInetAddress_supports_ipv4() { assertThat(underTest.toInetAddress("1.2.3.4")).isNotEmpty(); } @Test - public void toInetAddress_supports_ipv6() { + void toInetAddress_supports_ipv6() { assertThat(underTest.toInetAddress("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb")).isNotEmpty(); assertThat(underTest.toInetAddress("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]")).isNotEmpty(); } @Test - public void toInetAddress_returns_empty_on_unvalid_IP_and_hostname() { + void toInetAddress_returns_empty_on_unvalid_IP_and_hostname() { assertThat(underTest.toInetAddress(secure().nextAlphabetic(32))).isEmpty(); } @Test - public void isLoopback_returns_true_on_loopback_address_or_host() { + void isLoopback_returns_true_on_loopback_address_or_host() { InetAddress loopback = InetAddress.getLoopbackAddress(); assertThat(underTest.isLoopback(loopback.getHostAddress())).isTrue(); @@ -122,7 +123,7 @@ public class NetworkUtilsImplTest { } @Test - public void isLoopback_returns_true_on_localhost_address_or_host_if_loopback() { + void isLoopback_returns_true_on_localhost_address_or_host_if_loopback() { try { InetAddress localHost = InetAddress.getLocalHost(); boolean isLoopback = localHost.isLoopbackAddress(); @@ -134,7 +135,7 @@ public class NetworkUtilsImplTest { } @Test - public void isLocal_returns_true_on_loopback_address_or_host() { + void isLocal_returns_true_on_loopback_address_or_host() { InetAddress loopback = InetAddress.getLoopbackAddress(); assertThat(underTest.isLocal(loopback.getHostAddress())).isTrue(); @@ -142,7 +143,7 @@ public class NetworkUtilsImplTest { } @Test - public void isLocal_returns_true_on_localhost_address_or_host() { + void isLocal_returns_true_on_localhost_address_or_host() { try { InetAddress localHost = InetAddress.getLocalHost(); @@ -152,4 +153,18 @@ public class NetworkUtilsImplTest { // ignore, host running the test has no localhost } } + + @Test + void isIpv6Address_returnsAccordingToAddressAndIPv6Preference() { + System.setProperty(PREFER_IPV_6_ADDRESSES, "false"); + assertThat(underTest.isIpv6Address("2001:db8:3::91")).isTrue(); + assertThat(underTest.isIpv6Address("192.168.1.1")).isFalse(); + assertThat(underTest.isIpv6Address("")).isFalse(); + + System.setProperty(PREFER_IPV_6_ADDRESSES, "true"); + assertThat(underTest.isIpv6Address("2001:db8:3::91")).isTrue(); + assertThat(underTest.isIpv6Address("192.168.1.1")).isFalse(); + assertThat(underTest.isIpv6Address("")).isTrue(); + } + } diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 6e677fcfd0b..399170da48e 100644 --- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -199,6 +199,7 @@ import org.sonar.server.platform.db.CheckAnyonePermissionsAtStartup; import org.sonar.server.platform.db.migration.DatabaseMigrationPersister; import org.sonar.server.platform.db.migration.DatabaseMigrationTelemetry; import org.sonar.server.platform.telemetry.TelemetryFipsEnabledProvider; +import org.sonar.server.platform.telemetry.TelemetryIpv6EnabledProvider; import org.sonar.server.platform.telemetry.TelemetryMQRModePropertyProvider; import org.sonar.server.platform.telemetry.TelemetryNclocProvider; import org.sonar.server.platform.telemetry.TelemetryPortfolioSelectionModeProvider; @@ -692,6 +693,7 @@ public class PlatformLevel4 extends PlatformLevel { TelemetryNclocProvider.class, TelemetryUserEnabledProvider.class, TelemetryFipsEnabledProvider.class, + TelemetryIpv6EnabledProvider.class, TelemetrySubportfolioSelectionModeProvider.class, TelemetryPortfolioSelectionModeProvider.class, TelemetryApplicationsCountProvider.class, diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProvider.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProvider.java new file mode 100644 index 00000000000..47043be1786 --- /dev/null +++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProvider.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.telemetry; + +import java.util.Optional; +import org.sonar.api.config.Configuration; +import org.sonar.process.NetworkUtils; +import org.sonar.process.ProcessProperties; +import org.sonar.telemetry.core.AbstractTelemetryDataProvider; +import org.sonar.telemetry.core.Dimension; +import org.sonar.telemetry.core.Granularity; +import org.sonar.telemetry.core.TelemetryDataType; + +public class TelemetryIpv6EnabledProvider extends AbstractTelemetryDataProvider<Integer> { + + final NetworkUtils networkUtils; + + private final Configuration configuration; + + public TelemetryIpv6EnabledProvider(NetworkUtils networkUtils, Configuration configuration) { + super("internet_protocol_version", Dimension.INSTALLATION, Granularity.MONTHLY, TelemetryDataType.INTEGER); + this.networkUtils = networkUtils; + this.configuration = configuration; + } + + @Override + public Optional<Integer> getValue() { + if (isIpv6Address()) { + return Optional.of(6); + } else { + return Optional.of(4); + } + } + + private boolean isIpv6Address() { + String ipAddress = configuration.get(ProcessProperties.Property.WEB_HOST.getKey()).orElse(""); + return networkUtils.isIpv6Address(ipAddress); + } +} diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProviderTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProviderTest.java new file mode 100644 index 00000000000..049268ae3e7 --- /dev/null +++ b/server/sonar-webserver/src/test/java/org/sonar/server/platform/telemetry/TelemetryIpv6EnabledProviderTest.java @@ -0,0 +1,108 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.telemetry; + +import java.net.InetAddress; +import java.util.Optional; +import java.util.OptionalInt; +import java.util.function.Predicate; +import org.junit.jupiter.api.Test; +import org.sonar.api.config.Configuration; +import org.sonar.process.NetworkUtils; +import org.sonar.telemetry.core.Dimension; +import org.sonar.telemetry.core.Granularity; +import org.sonar.telemetry.core.TelemetryDataType; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + +class TelemetryIpv6EnabledProviderTest { + + private final Configuration configuration = mock(Configuration.class); + + @Test + void testGetters_whenIpv6Address(){ + assertIpVersion(6); + } + + @Test + void testGetters_whenIpv4Address(){ + assertIpVersion(4); + } + + void assertIpVersion(Integer expectedProtocol) { + TelemetryIpv6EnabledProvider underTest = new TelemetryIpv6EnabledProvider(new NetworkUtilsTestImpl(6 == expectedProtocol), configuration); + assertEquals("internet_protocol_version", underTest.getMetricKey()); + assertEquals(Dimension.INSTALLATION, underTest.getDimension()); + assertEquals(Granularity.MONTHLY, underTest.getGranularity()); + assertEquals(TelemetryDataType.INTEGER, underTest.getType()); + assertEquals(Optional.of(expectedProtocol), underTest.getValue()); + } + + static class NetworkUtilsTestImpl implements NetworkUtils { + + private final boolean isIpv6Address; + + public NetworkUtilsTestImpl(boolean isIpv6Address) { + this.isIpv6Address = isIpv6Address; + } + + @Override + public int getNextLoopbackAvailablePort() { + return 0; + } + + @Override + public OptionalInt getNextAvailablePort(String hostOrAddress) { + return OptionalInt.empty(); + } + + @Override + public String getHostname() { + return ""; + } + + @Override + public Optional<InetAddress> toInetAddress(String hostOrAddress) { + return Optional.empty(); + } + + @Override + public boolean isLocal(String hostOrAddress) { + return false; + } + + @Override + public boolean isLoopback(String hostOrAddress) { + return false; + } + + @Override + public boolean isIpv6Address(String hostOrAddress) { + return isIpv6Address; + } + + @Override + public Optional<InetAddress> getLocalInetAddress(Predicate<InetAddress> predicate) { + return Optional.empty(); + } + } + +} |