All Downloads are FREE. Search and download functionalities are using the official Maven repository.

zipkin2.Endpoint Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package zipkin2;

import java.io.ObjectStreamException;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Locale;
import zipkin2.internal.Nullable;
import zipkin2.internal.Platform;

import static zipkin2.internal.HexCodec.HEX_DIGITS;

/** The network context of a node in the service graph. */
//@Immutable
public final class Endpoint implements Serializable { // for Spark and Flink jobs
  private static final long serialVersionUID = 0L;

  /**
   * Lower-case label of this node in the service graph, such as "favstar". Leave absent if
   * unknown.
   *
   * 

This is a primary label for trace lookup and aggregation, so it should be intuitive and * consistent. Many use a name from service discovery. */ @Nullable public String serviceName() { return serviceName; } /** * The text representation of the primary IPv4 address associated with this a connection. Ex. * 192.168.99.100 Absent if unknown. */ @Nullable public String ipv4() { return ipv4; } /** * IPv4 endpoint address packed into 4 bytes or null if unknown. * * @see #ipv6() * @see java.net.Inet4Address#getAddress() */ @Nullable public byte[] ipv4Bytes() { return ipv4Bytes; } /** * The text representation of the primary IPv6 address associated with this a connection. Ex. * 2001:db8::c001 Absent if unknown. * * @see #ipv4() for mapped addresses * @see #ipv6Bytes() */ @Nullable public String ipv6() { return ipv6; } /** * IPv6 endpoint address packed into 16 bytes or null if unknown. * * @see #ipv6() * @see java.net.Inet6Address#getAddress() */ @Nullable public byte[] ipv6Bytes() { return ipv6Bytes; } /** * Port of the IP's socket or null, if not known. * * @see java.net.InetSocketAddress#getPort() */ @Nullable public Integer port() { return port != 0 ? port : null; } /** * Like {@link #port()} except returns a primitive where zero implies absent. * *

Using this method will avoid allocation, so is encouraged when copying data. */ public int portAsInt() { return port; } public Builder toBuilder() { return new Builder(this); } public static Builder newBuilder() { return new Builder(); } public static final class Builder { String serviceName, ipv4, ipv6; byte[] ipv4Bytes, ipv6Bytes; int port; // zero means null Builder(Endpoint source) { serviceName = source.serviceName; ipv4 = source.ipv4; ipv6 = source.ipv6; ipv4Bytes = source.ipv4Bytes; ipv6Bytes = source.ipv6Bytes; port = source.port; } Builder merge(Endpoint source) { if (serviceName == null) serviceName = source.serviceName; if (ipv4 == null) ipv4 = source.ipv4; if (ipv6 == null) ipv6 = source.ipv6; if (ipv4Bytes == null) ipv4Bytes = source.ipv4Bytes; if (ipv6Bytes == null) ipv6Bytes = source.ipv6Bytes; if (port == 0) port = source.port; return this; } /** @see Endpoint#serviceName */ public Builder serviceName(@Nullable String serviceName) { this.serviceName = serviceName == null || serviceName.isEmpty() ? null : serviceName.toLowerCase(Locale.ROOT); return this; } /** Chaining variant of {@link #parseIp(InetAddress)} */ public Builder ip(@Nullable InetAddress addr) { parseIp(addr); return this; } /** * Returns true if {@link Endpoint#ipv4()} or {@link Endpoint#ipv6()} could be parsed from the * input. * *

Returns boolean not this for conditional parsing. For example: *

{@code
     * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
     *   builder.parseIp(input.getRemoteAddr());
     * }
     * }
* * @see #parseIp(String) */ public final boolean parseIp(@Nullable InetAddress addr) { if (addr == null) return false; if (addr instanceof Inet4Address) { ipv4 = addr.getHostAddress(); ipv4Bytes = addr.getAddress(); } else if (addr instanceof Inet6Address) { byte[] addressBytes = addr.getAddress(); if (!parseEmbeddedIPv4(addressBytes)) { ipv6 = writeIpV6(addressBytes); ipv6Bytes = addressBytes; } } else { return false; } return true; } /** * Like {@link #parseIp(String)} except this accepts a byte array. * * @param ipBytes byte array whose ownership is exclusively transferred to this endpoint. */ public final boolean parseIp(byte[] ipBytes) { if (ipBytes == null) return false; if (ipBytes.length == 4) { ipv4Bytes = ipBytes; ipv4 = writeIpV4(ipBytes); } else if (ipBytes.length == 16) { if (!parseEmbeddedIPv4(ipBytes)) { ipv6 = writeIpV6(ipBytes); ipv6Bytes = ipBytes; } } else { return false; } return true; } static String writeIpV4(byte[] ipBytes) { char[] buf = Platform.shortStringBuffer(); int pos = 0; pos = writeBackwards(ipBytes[0] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[1] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[2] & 0xff, pos, buf); buf[pos++] = '.'; pos = writeBackwards(ipBytes[3] & 0xff, pos, buf); return new String(buf, 0, pos); } static int writeBackwards(int b, int pos, char[] buf) { if (b < 10) { buf[pos] = HEX_DIGITS[b]; return pos + 1; } int i = pos += b < 100 ? 2 : 3; // We write backwards from right to left. while (b != 0) { int digit = b % 10; buf[--i] = HEX_DIGITS[digit]; b /= 10; } return pos; } /** Chaining variant of {@link #parseIp(String)} */ public Builder ip(@Nullable String ipString) { parseIp(ipString); return this; } /** * Returns true if {@link Endpoint#ipv4()} or {@link Endpoint#ipv6()} could be parsed from the * input. * *

Returns boolean not this for conditional parsing. For example: *

{@code
     * if (!builder.parseIp(input.getHeader("X-Forwarded-For"))) {
     *   builder.parseIp(input.getRemoteAddr());
     * }
     * }
* * @see #parseIp(InetAddress) */ public final boolean parseIp(@Nullable String ipString) { if (ipString == null || ipString.isEmpty()) return false; IpFamily format = detectFamily(ipString); if (format == IpFamily.IPv4) { ipv4 = ipString; ipv4Bytes = getIpv4Bytes(ipv4); } else if (format == IpFamily.IPv4Embedded) { ipv4 = ipString.substring(ipString.lastIndexOf(':') + 1); ipv4Bytes = getIpv4Bytes(ipv4); } else if (format == IpFamily.IPv6) { byte[] addressBytes = textToNumericFormatV6(ipString); if (addressBytes == null) return false; ipv6 = writeIpV6(addressBytes); // ensures consistent format ipv6Bytes = addressBytes; } else { return false; } return true; } /** * Use this to set the port to an externally defined value. * * @param port port associated with the endpoint. zero coerces to null (unknown) * @see Endpoint#port() */ public Builder port(@Nullable Integer port) { if (port != null) { if (port > 0xffff) throw new IllegalArgumentException("invalid port " + port); if (port <= 0) port = 0; } this.port = port != null ? port : 0; return this; } /** @see Endpoint#portAsInt() */ public Builder port(int port) { if (port > 0xffff) throw new IllegalArgumentException("invalid port " + port); if (port < 0) port = 0; this.port = port; return this; } public Endpoint build() { return new Endpoint(this); } Builder() { } boolean parseEmbeddedIPv4(byte[] ipv6) { for (int i = 0; i < 10; i++) { // Embedded IPv4 addresses start with unset 80 bits if (ipv6[i] != 0) return false; } int flag = (ipv6[10] & 0xff) << 8 | (ipv6[11] & 0xff); if (flag != 0) return false; // IPv4-Compatible or IPv4-Mapped byte o1 = ipv6[12], o2 = ipv6[13], o3 = ipv6[14], o4 = ipv6[15]; if (o1 == 0 && o2 == 0 && o3 == 0 && o4 == 1) { return false; // ::1 is localhost, not an embedded compat address } ipv4 = String.valueOf(o1 & 0xff) + '.' + (o2 & 0xff) + '.' + (o3 & 0xff) + '.' + (o4 & 0xff); ipv4Bytes = new byte[] {o1, o2, o3, o4}; return true; } } enum IpFamily { Unknown, IPv4, IPv4Embedded, IPv6 } /** * Adapted from code in {@code com.google.common.net.InetAddresses.ipStringToBytes}. This version * separates detection from parsing and checks more carefully about embedded addresses. */ static IpFamily detectFamily(String ipString) { boolean hasColon = false; boolean hasDot = false; for (int i = 0, length = ipString.length(); i < length; i++) { char c = ipString.charAt(i); if (c == '.') { hasDot = true; } else if (c == ':') { if (hasDot) return IpFamily.Unknown; // Colons must not appear after dots. hasColon = true; } else if (notHex(c)) { return IpFamily.Unknown; // Everything else must be a decimal or hex digit. } } // Now decide which address family to parse. if (hasColon) { if (hasDot) { int lastColonIndex = ipString.lastIndexOf(':'); if (!isValidIpV4Address(ipString, lastColonIndex + 1, ipString.length())) { return IpFamily.Unknown; } if (lastColonIndex == 1 && ipString.charAt(0) == ':') {// compressed like ::1.2.3.4 return IpFamily.IPv4Embedded; } if (lastColonIndex != 6 || ipString.charAt(0) != ':' || ipString.charAt(1) != ':') { return IpFamily.Unknown; } for (int i = 2; i < 6; i++) { char c = ipString.charAt(i); if (c != 'f' && c != 'F' && c != '0') return IpFamily.Unknown; } return IpFamily.IPv4Embedded; } return IpFamily.IPv6; } else if (hasDot && isValidIpV4Address(ipString, 0, ipString.length())) { return IpFamily.IPv4; } return IpFamily.Unknown; } static boolean notHex(char c) { return (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F'); } static String writeIpV6(byte[] ipv6) { int pos = 0; char[] buf = Platform.shortStringBuffer(); // Compress the longest string of zeros int zeroCompressionIndex = -1; int zeroCompressionLength = -1; int zeroIndex = -1; boolean allZeros = true; for (int i = 0; i < ipv6.length; i += 2) { if (ipv6[i] == 0 && ipv6[i + 1] == 0) { if (zeroIndex < 0) zeroIndex = i; continue; } allZeros = false; if (zeroIndex >= 0) { int zeroLength = i - zeroIndex; if (zeroLength > zeroCompressionLength) { zeroCompressionIndex = zeroIndex; zeroCompressionLength = zeroLength; } zeroIndex = -1; } } // handle all zeros: 0:0:0:0:0:0:0:0 -> :: if (allZeros) return "::"; // handle trailing zeros: 2001:0:0:4:0:0:0:0 -> 2001:0:0:4:: if (zeroCompressionIndex == -1 && zeroIndex != -1) { zeroCompressionIndex = zeroIndex; zeroCompressionLength = 16 - zeroIndex; } int i = 0; while (i < ipv6.length) { if (i == zeroCompressionIndex) { buf[pos++] = ':'; i += zeroCompressionLength; if (i == ipv6.length) buf[pos++] = ':'; continue; } if (i != 0) buf[pos++] = ':'; byte high = ipv6[i++]; byte low = ipv6[i++]; // handle leading zeros: 2001:0:0:4:0000:0:0:8 -> 2001:0:0:4::8 boolean leadingZero; char val = HEX_DIGITS[(high >> 4) & 0xf]; if (!(leadingZero = val == '0')) buf[pos++] = val; val = HEX_DIGITS[high & 0xf]; if (!(leadingZero = (leadingZero && val == '0'))) buf[pos++] = val; val = HEX_DIGITS[(low >> 4) & 0xf]; if (!(leadingZero && val == '0')) buf[pos++] = val; buf[pos++] = HEX_DIGITS[low & 0xf]; } return new String(buf, 0, pos); } // Begin code from com.google.common.net.InetAddresses 23 static final int IPV6_PART_COUNT = 8; @Nullable static byte[] textToNumericFormatV6(String ipString) { // An address can have [2..8] colons, and N colons make N+1 parts. String[] parts = ipString.split(":", IPV6_PART_COUNT + 2); if (parts.length < 3 || parts.length > IPV6_PART_COUNT + 1) { return null; } // Disregarding the endpoints, find "::" with nothing in between. // This indicates that a run of zeroes has been skipped. int skipIndex = -1; for (int i = 1; i < parts.length - 1; i++) { if (parts[i].length() == 0) { if (skipIndex >= 0) { return null; // Can't have more than one :: } skipIndex = i; } } int partsHi; // Number of parts to copy from above/before the "::" int partsLo; // Number of parts to copy from below/after the "::" if (skipIndex >= 0) { // If we found a "::", then check if it also covers the endpoints. partsHi = skipIndex; partsLo = parts.length - skipIndex - 1; if (parts[0].length() == 0 && --partsHi != 0) { return null; // ^: requires ^:: } if (parts[parts.length - 1].length() == 0 && --partsLo != 0) { return null; // :$ requires ::$ } } else { // Otherwise, allocate the entire address to partsHi. The endpoints // could still be empty, but parseHextet() will check for that. partsHi = parts.length; partsLo = 0; } // If we found a ::, then we must have skipped at least one part. // Otherwise, we must have exactly the right number of parts. int partsSkipped = IPV6_PART_COUNT - (partsHi + partsLo); if (!(skipIndex >= 0 ? partsSkipped >= 1 : partsSkipped == 0)) { return null; } // Now parse the hextets into a byte array. ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT); try { for (int i = 0; i < partsHi; i++) { rawBytes.putShort(parseHextet(parts[i])); } for (int i = 0; i < partsSkipped; i++) { rawBytes.putShort((short) 0); } for (int i = partsLo; i > 0; i--) { rawBytes.putShort(parseHextet(parts[parts.length - i])); } } catch (NumberFormatException ex) { return null; } return rawBytes.array(); } static short parseHextet(String ipPart) { // Note: we already verified that this string contains only hex digits. int hextet = Integer.parseInt(ipPart, 16); if (hextet > 0xffff) { throw new NumberFormatException(); } return (short) hextet; } // End code from com.google.common.net.InetAddresses 23 // Begin code from io.netty.util.NetUtil 4.1 static boolean isValidIpV4Address(String ip, int from, int toExcluded) { int len = toExcluded - from; int i; return len <= 15 && len >= 7 && (i = ip.indexOf('.', from + 1)) > 0 && isValidIpV4Word(ip, from, i) && (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && (i = ip.indexOf('.', from = i + 2)) > 0 && isValidIpV4Word(ip, from - 1, i) && isValidIpV4Word(ip, i + 1, toExcluded); } static boolean isValidIpV4Word(CharSequence word, int from, int toExclusive) { int len = toExclusive - from; char c0, c1, c2; if (len < 1 || len > 3 || (c0 = word.charAt(from)) < '0') { return false; } if (len == 3) { return (c1 = word.charAt(from + 1)) >= '0' && (c2 = word.charAt(from + 2)) >= '0' && ((c0 <= '1' && c1 <= '9' && c2 <= '9') || (c0 == '2' && c1 <= '5' && (c2 <= '5' || (c1 < '5' && c2 <= '9')))); } return c0 <= '9' && (len == 1 || isValidNumericChar(word.charAt(from + 1))); } static boolean isValidNumericChar(char c) { return c >= '0' && c <= '9'; } // End code from io.netty.util.NetUtil 4.1 // clutter below mainly due to difficulty working with Kryo which cannot handle AutoValue subclass // See https://github.com/incubator-zipkin/issues/1879 final String serviceName, ipv4, ipv6; final byte[] ipv4Bytes, ipv6Bytes; final int port; Endpoint(Builder builder) { serviceName = builder.serviceName; ipv4 = builder.ipv4; ipv4Bytes = builder.ipv4Bytes; ipv6 = builder.ipv6; ipv6Bytes = builder.ipv6Bytes; port = builder.port; } Endpoint(SerializedForm serializedForm) { serviceName = serializedForm.serviceName; ipv4 = serializedForm.ipv4; ipv4Bytes = serializedForm.ipv4Bytes; ipv6 = serializedForm.ipv6; ipv6Bytes = serializedForm.ipv6Bytes; port = serializedForm.port; } @Override public String toString() { return "Endpoint{" + "serviceName=" + serviceName + ", " + "ipv4=" + ipv4 + ", " + "ipv6=" + ipv6 + ", " + "port=" + port + "}"; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Endpoint)) return false; Endpoint that = (Endpoint) o; return ((serviceName == null) ? (that.serviceName == null) : serviceName.equals(that.serviceName)) && ((ipv4 == null) ? (that.ipv4 == null) : ipv4.equals(that.ipv4)) && ((ipv6 == null) ? (that.ipv6 == null) : ipv6.equals(that.ipv6)) && port == that.port; } @Override public int hashCode() { int h = 1; h *= 1000003; h ^= (serviceName == null) ? 0 : serviceName.hashCode(); h *= 1000003; h ^= (ipv4 == null) ? 0 : ipv4.hashCode(); h *= 1000003; h ^= (ipv6 == null) ? 0 : ipv6.hashCode(); h *= 1000003; h ^= port; return h; } // As this is an immutable object (no default constructor), defer to a serialization proxy. final Object writeReplace() throws ObjectStreamException { return new SerializedForm(this); } // TODO: replace this with native proto3 encoding static final class SerializedForm implements Serializable { static final long serialVersionUID = 0L; final String serviceName, ipv4, ipv6; final byte[] ipv4Bytes, ipv6Bytes; final int port; SerializedForm(Endpoint endpoint) { serviceName = endpoint.serviceName; ipv4 = endpoint.ipv4; ipv4Bytes = endpoint.ipv4Bytes; ipv6 = endpoint.ipv6; ipv6Bytes = endpoint.ipv6Bytes; port = endpoint.port; } Object readResolve() throws ObjectStreamException { try { return new Endpoint(this); } catch (IllegalArgumentException e) { throw new StreamCorruptedException(e.getMessage()); } } } static byte[] getIpv4Bytes(String ipv4) { byte[] result = new byte[4]; int pos = 0; for (int i = 0, len = ipv4.length(); i < len; ) { char ch = ipv4.charAt(i++); int octet = ch - '0'; if (i == len || (ch = ipv4.charAt(i++)) == '.') { // then we have a single digit octet result[pos++] = (byte) octet; continue; } // push the decimal octet = (octet * 10) + (ch - '0'); if (i == len || (ch = ipv4.charAt(i++)) == '.') { // then we have a two digit octet result[pos++] = (byte) octet; continue; } // otherwise, we have a three digit octet octet = (octet * 10) + (ch - '0'); result[pos++] = (byte) octet; i++; // skip the dot } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy