|
| 1 | +package com.gitblit.instance; |
| 2 | + |
| 3 | +import com.gitblit.utils.FileUtils; |
| 4 | +import org.slf4j.Logger; |
| 5 | +import org.slf4j.LoggerFactory; |
| 6 | + |
| 7 | +import java.io.File; |
| 8 | +import java.io.IOException; |
| 9 | +import java.net.InetAddress; |
| 10 | +import java.net.NetworkInterface; |
| 11 | +import java.net.Socket; |
| 12 | +import java.util.ArrayList; |
| 13 | +import java.util.Enumeration; |
| 14 | +import java.util.List; |
| 15 | +import java.util.UUID; |
| 16 | + |
| 17 | +/** |
| 18 | + * The instance id is a unique identifier for an installed Gitblit instance. |
| 19 | + * |
| 20 | + * This is used to track the number of Gitblit instances in the field. |
| 21 | + * Its purpose is to gauge the popularity of Gitblit and to help |
| 22 | + * prioritize feature requests. |
| 23 | + * |
| 24 | + * The instance id should be unique between different instances, even |
| 25 | + * on the same machine. But it should stay the same between restarts of |
| 26 | + * the same instance. It should also stay the same between upgrades of |
| 27 | + * the same instance. Therefore, it must be stored in a file that is |
| 28 | + * not overwritten during upgrades, once it has been created. |
| 29 | + */ |
| 30 | +public class GitblitInstanceId |
| 31 | +{ |
| 32 | + static final String STORAGE_FILE = "gbins"; |
| 33 | + |
| 34 | + private final Logger log = LoggerFactory.getLogger(getClass()); |
| 35 | + |
| 36 | + private final File idFileBase; |
| 37 | + |
| 38 | + private UUID id; |
| 39 | + |
| 40 | + |
| 41 | + /** |
| 42 | + * Constructor. |
| 43 | + */ |
| 44 | + public GitblitInstanceId() |
| 45 | + { |
| 46 | + this.idFileBase = null; |
| 47 | + } |
| 48 | + |
| 49 | + /** |
| 50 | + * Constructor. |
| 51 | + */ |
| 52 | + public GitblitInstanceId(File idFileBase) |
| 53 | + { |
| 54 | + this.idFileBase = idFileBase; |
| 55 | + } |
| 56 | + |
| 57 | + |
| 58 | + /** |
| 59 | + * Get the instance id. |
| 60 | + * |
| 61 | + * @return the instance id. |
| 62 | + */ |
| 63 | + public UUID getId() { |
| 64 | + if (this.id == null) { |
| 65 | + load(); |
| 66 | + } |
| 67 | + return this.id; |
| 68 | + } |
| 69 | + |
| 70 | + |
| 71 | + /** |
| 72 | + * Load the instance id from the file. |
| 73 | + */ |
| 74 | + private void load() |
| 75 | + { |
| 76 | + if (this.idFileBase == null) { |
| 77 | + // Not working with stored id. |
| 78 | + log.debug("No id file base directory specified. Generated id is not persisted."); |
| 79 | + generate(); |
| 80 | + return; |
| 81 | + } |
| 82 | + |
| 83 | + File idFile = new File(this.idFileBase, STORAGE_FILE); |
| 84 | + if (idFile.exists()) { |
| 85 | + // Read the file |
| 86 | + String uuidString = readFromFile(idFile); |
| 87 | + |
| 88 | + // Parse the UUID |
| 89 | + try { |
| 90 | + this.id = UUID.fromString(uuidString); |
| 91 | + return; |
| 92 | + } |
| 93 | + catch (IllegalArgumentException e) { |
| 94 | + log.debug("Unable to parse instance id. Will generate a new one: {}", e.getMessage(), e); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + // Generate a new instance id and persist it to disk. |
| 99 | + generate(); |
| 100 | + storeToFile(idFile); |
| 101 | + } |
| 102 | + |
| 103 | + |
| 104 | + private String readFromFile(File idfile) |
| 105 | + { |
| 106 | +// log.debug("Loading instance id from file: {}", idfile.getAbsolutePath()); |
| 107 | + |
| 108 | + String string = FileUtils.readContent(idfile, null).trim(); |
| 109 | + String uuidString = string.replaceAll("\\s+",""); |
| 110 | + return uuidString.trim(); |
| 111 | + } |
| 112 | + |
| 113 | + private void storeToFile(File idfile) |
| 114 | + { |
| 115 | + // Make sure that the directory exists |
| 116 | + if (!idfile.getParentFile().exists()) { |
| 117 | + if (!idfile.getParentFile().mkdirs()) { |
| 118 | + log.debug("Unable to create directory for instance id file: {}", idfile.getParentFile().getAbsolutePath()); |
| 119 | + return; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + // Write the UUID to the file |
| 124 | + String uuidString = this.id.toString(); |
| 125 | + FileUtils.writeContent(idfile, uuidString); |
| 126 | + } |
| 127 | + |
| 128 | + |
| 129 | + /** |
| 130 | + * Generate a new instance id and persist it to disk. |
| 131 | + * |
| 132 | + * UUID is variant, i.e. OSF DCE, version 8, a custom format. |
| 133 | + * xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
| 134 | + * date -rand-8rnd-8rnd- rand OUI |
| 135 | + * |
| 136 | + * The variant nibble has the variant (1) in the upper two bits as 0b10xx, |
| 137 | + * and the lower two bits are used as a version, currently 0bxx00. |
| 138 | + * Should the format of this UUID change, the version can be incremented |
| 139 | + * to 0bxx01 or 0bxx10. Further increments would set the bits in the variant |
| 140 | + * nibble to 0bxx11 and employ more bits from the next nibble for further |
| 141 | + * differentiation. |
| 142 | + */ |
| 143 | + private void generate() |
| 144 | + { |
| 145 | + // Start with a random UUID |
| 146 | + UUID id = UUID.randomUUID(); |
| 147 | + long upper = id.getMostSignificantBits(); |
| 148 | + long lower = id.getLeastSignificantBits(); |
| 149 | + |
| 150 | + |
| 151 | + // Set the variant bits to 0b1000, variant 1, our version 0. |
| 152 | + lower &= 0x0FFFFFFFFFFFFFFFL; // Clear the variant bits |
| 153 | + lower |= 0x8000000000000000L; // Set the variant bits to 0b1000 |
| 154 | + |
| 155 | + // Set the version bits to 0b1000, version 8. |
| 156 | + upper &= 0xFFFFFFFFFFFF0FFFL; // Clear the version bits |
| 157 | + upper |= 0x0000000000008000L; // Set the version bits to 0b1000 |
| 158 | + |
| 159 | + |
| 160 | + // Set the first four bytes to represent the date. |
| 161 | + long date = System.currentTimeMillis(); |
| 162 | + date &= 0xFFFFFFFFFFFF0000L; // Clear the last two bytes, those are only a few minutes. |
| 163 | + date <<= 2 * 8; // We do not need the upper two bytes, that is too far into the future. |
| 164 | + |
| 165 | + upper &= 0x00000000FFFFFFFFL; // Clear the date bits. |
| 166 | + upper |= date; // Set the date in the upper 32 bits. |
| 167 | + |
| 168 | + |
| 169 | + // Set the OUI in the lower three bytes. |
| 170 | + Long oui = getNodeOUI(); |
| 171 | + if (oui != null) { |
| 172 | + lower &= 0xFFFFFFFFFF000000L; // Clear the OUI bits. |
| 173 | + lower |= (0x1000000L | oui); // Set the OUI in the lower three bytes. Mark as valid OUI in bit above them. |
| 174 | + } |
| 175 | + else { |
| 176 | + // Mark this as an invalid OUI, i.e. random bits, by setting the bit above the OUI bits to zero. |
| 177 | + lower &= 0xFFFFFFFFFEFFFFFFL; // Clear the valid OUI indicator bit. |
| 178 | + } |
| 179 | + |
| 180 | + this.id = new UUID(upper, lower); |
| 181 | + } |
| 182 | + |
| 183 | + |
| 184 | + /** |
| 185 | + * Get the OUI of one NIC of this host. |
| 186 | + * |
| 187 | + * @return null if no OUI could be detected, otherwise the OUI in the lower three bytes of a Long. |
| 188 | + */ |
| 189 | + private Long getNodeOUI() |
| 190 | + { |
| 191 | + byte[] node = null; |
| 192 | + String logPrefix = "Unable to detect host. Use random value."; |
| 193 | + |
| 194 | + try { |
| 195 | + InetAddress ipa = InetAddress.getLocalHost(); |
| 196 | + NetworkInterface iface = NetworkInterface.getByInetAddress(ipa); |
| 197 | + if (iface != null) { |
| 198 | + node = iface.getHardwareAddress(); |
| 199 | + logPrefix = "From getLocalHost:"; |
| 200 | + } |
| 201 | + |
| 202 | + if (node == null) { |
| 203 | + List<byte[]> macs = new ArrayList<>(); |
| 204 | + List<byte[]> offmacs = new ArrayList<>(); |
| 205 | + Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); |
| 206 | + while (interfaces.hasMoreElements()) { |
| 207 | + iface = interfaces.nextElement(); |
| 208 | + byte[] mac = iface.getHardwareAddress(); |
| 209 | + if (mac != null) { |
| 210 | + if (iface.isLoopback()) { |
| 211 | + continue; |
| 212 | + } |
| 213 | + if (iface.isVirtual() || iface.isPointToPoint()) { |
| 214 | + continue; |
| 215 | + } |
| 216 | + if (iface.isUp()) { |
| 217 | + macs.add(mac); |
| 218 | + } |
| 219 | + else { |
| 220 | + offmacs.add(mac); |
| 221 | + } |
| 222 | + } |
| 223 | + } |
| 224 | + |
| 225 | + if (macs.size() == 1) { |
| 226 | + node = macs.get(0); |
| 227 | + logPrefix = "From up iface:"; |
| 228 | + } |
| 229 | + else if (offmacs.size() == 1) { |
| 230 | + node = offmacs.get(0); |
| 231 | + logPrefix = "From down iface:"; |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + if (node == null) { |
| 236 | + Socket socket = new Socket("www.gitblit.dev", 80); |
| 237 | + ipa = socket.getLocalAddress(); |
| 238 | + socket.close(); |
| 239 | + iface = NetworkInterface.getByInetAddress(ipa); |
| 240 | + if (iface != null) { |
| 241 | + node = iface.getHardwareAddress(); |
| 242 | + logPrefix = "From socket:"; |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + if (node == null) { |
| 247 | + log.debug(logPrefix); |
| 248 | + return null; |
| 249 | + } |
| 250 | + |
| 251 | + if (log.isDebugEnabled()) { |
| 252 | + log.debug("{} {}", logPrefix, String.format("%02X:%02X:%02X", node[0], node[1], node[2])); |
| 253 | + } |
| 254 | + |
| 255 | + long l = (((long)node[0]) << 16) & 0xff0000; |
| 256 | + l |= (((long)node[1]) << 8) & 0xff00; |
| 257 | + l |= ((long)node[2]) & 0xff; |
| 258 | + return l; |
| 259 | + } |
| 260 | + catch (IOException e) { |
| 261 | + log.debug("Exception while getting OUI: {}", e.getMessage(), e); |
| 262 | + return null; |
| 263 | + } |
| 264 | + } |
| 265 | +} |
0 commit comments