1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.haproxy;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol.AddressFamily;
20 import io.netty.util.AbstractReferenceCounted;
21 import io.netty.util.CharsetUtil;
22 import io.netty.util.NetUtil;
23 import io.netty.util.ResourceLeakDetector;
24 import io.netty.util.ResourceLeakDetectorFactory;
25 import io.netty.util.ResourceLeakTracker;
26 import io.netty.util.internal.ObjectUtil;
27 import io.netty.util.internal.StringUtil;
28
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.List;
32
33
34
35
36 public final class HAProxyMessage extends AbstractReferenceCounted {
37
38
39 private static final int MAX_NESTING_LEVEL = 128;
40 private static final ResourceLeakDetector<HAProxyMessage> leakDetector =
41 ResourceLeakDetectorFactory.instance().newResourceLeakDetector(HAProxyMessage.class);
42
43 private final ResourceLeakTracker<HAProxyMessage> leak;
44 private final HAProxyProtocolVersion protocolVersion;
45 private final HAProxyCommand command;
46 private final HAProxyProxiedProtocol proxiedProtocol;
47 private final String sourceAddress;
48 private final String destinationAddress;
49 private final int sourcePort;
50 private final int destinationPort;
51 private final List<HAProxyTLV> tlvs;
52
53
54
55
56 private HAProxyMessage(
57 HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
58 String sourceAddress, String destinationAddress, String sourcePort, String destinationPort) {
59 this(
60 protocolVersion, command, proxiedProtocol,
61 sourceAddress, destinationAddress, portStringToInt(sourcePort), portStringToInt(destinationPort));
62 }
63
64
65
66
67
68
69
70
71
72
73
74 public HAProxyMessage(
75 HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
76 String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) {
77
78 this(protocolVersion, command, proxiedProtocol,
79 sourceAddress, destinationAddress, sourcePort, destinationPort, Collections.<HAProxyTLV>emptyList());
80 }
81
82
83
84
85
86
87
88
89
90
91
92
93 public HAProxyMessage(
94 HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
95 String sourceAddress, String destinationAddress, int sourcePort, int destinationPort,
96 List<? extends HAProxyTLV> tlvs) {
97
98 ObjectUtil.checkNotNull(protocolVersion, "protocolVersion");
99 ObjectUtil.checkNotNull(proxiedProtocol, "proxiedProtocol");
100 ObjectUtil.checkNotNull(tlvs, "tlvs");
101 AddressFamily addrFamily = proxiedProtocol.addressFamily();
102
103 checkAddress(sourceAddress, addrFamily);
104 checkAddress(destinationAddress, addrFamily);
105 checkPort(sourcePort, addrFamily);
106 checkPort(destinationPort, addrFamily);
107
108 this.protocolVersion = protocolVersion;
109 this.command = command;
110 this.proxiedProtocol = proxiedProtocol;
111 this.sourceAddress = sourceAddress;
112 this.destinationAddress = destinationAddress;
113 this.sourcePort = sourcePort;
114 this.destinationPort = destinationPort;
115 this.tlvs = Collections.unmodifiableList(tlvs);
116
117 leak = leakDetector.track(this);
118 }
119
120
121
122
123
124
125
126
127 static HAProxyMessage decodeHeader(ByteBuf header) {
128 ObjectUtil.checkNotNull(header, "header");
129
130 if (header.readableBytes() < 16) {
131 throw new HAProxyProtocolException(
132 "incomplete header: " + header.readableBytes() + " bytes (expected: 16+ bytes)");
133 }
134
135
136 header.skipBytes(12);
137 final byte verCmdByte = header.readByte();
138
139 HAProxyProtocolVersion ver;
140 try {
141 ver = HAProxyProtocolVersion.valueOf(verCmdByte);
142 } catch (IllegalArgumentException e) {
143 throw new HAProxyProtocolException(e);
144 }
145
146 if (ver != HAProxyProtocolVersion.V2) {
147 throw new HAProxyProtocolException("version 1 unsupported: 0x" + Integer.toHexString(verCmdByte));
148 }
149
150 HAProxyCommand cmd;
151 try {
152 cmd = HAProxyCommand.valueOf(verCmdByte);
153 } catch (IllegalArgumentException e) {
154 throw new HAProxyProtocolException(e);
155 }
156
157 if (cmd == HAProxyCommand.LOCAL) {
158 return unknownMsg(HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL);
159 }
160
161
162 HAProxyProxiedProtocol protAndFam;
163 try {
164 protAndFam = HAProxyProxiedProtocol.valueOf(header.readByte());
165 } catch (IllegalArgumentException e) {
166 throw new HAProxyProtocolException(e);
167 }
168
169 if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
170 return unknownMsg(HAProxyProtocolVersion.V2, HAProxyCommand.PROXY);
171 }
172
173 int addressInfoLen = header.readUnsignedShort();
174
175 String srcAddress;
176 String dstAddress;
177 int addressLen;
178 int srcPort = 0;
179 int dstPort = 0;
180
181 AddressFamily addressFamily = protAndFam.addressFamily();
182
183 if (addressFamily == AddressFamily.AF_UNIX) {
184
185 if (addressInfoLen < 216 || header.readableBytes() < 216) {
186 throw new HAProxyProtocolException(
187 "incomplete UNIX socket address information: " +
188 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 216+ bytes)");
189 }
190 int startIdx = header.readerIndex();
191 int addressEnd = header.indexOf(startIdx, startIdx + 108, (byte) 0);
192 if (addressEnd == -1) {
193 addressLen = 108;
194 } else {
195 addressLen = addressEnd - startIdx;
196 }
197 srcAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
198
199 startIdx += 108;
200
201 addressEnd = header.indexOf(startIdx, startIdx + 108, (byte) 0);
202 if (addressEnd == -1) {
203 addressLen = 108;
204 } else {
205 addressLen = addressEnd - startIdx;
206 }
207 dstAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
208
209
210 header.readerIndex(startIdx + 108);
211 } else {
212 if (addressFamily == AddressFamily.AF_IPv4) {
213
214 if (addressInfoLen < 12 || header.readableBytes() < 12) {
215 throw new HAProxyProtocolException(
216 "incomplete IPv4 address information: " +
217 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 12+ bytes)");
218 }
219 addressLen = 4;
220 } else if (addressFamily == AddressFamily.AF_IPv6) {
221
222 if (addressInfoLen < 36 || header.readableBytes() < 36) {
223 throw new HAProxyProtocolException(
224 "incomplete IPv6 address information: " +
225 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 36+ bytes)");
226 }
227 addressLen = 16;
228 } else {
229 throw new HAProxyProtocolException(
230 "unable to parse address information (unknown address family: " + addressFamily + ')');
231 }
232
233
234 srcAddress = ipBytesToString(header, addressLen);
235 dstAddress = ipBytesToString(header, addressLen);
236 srcPort = header.readUnsignedShort();
237 dstPort = header.readUnsignedShort();
238 }
239
240 final List<HAProxyTLV> tlvs = readTlvs(header);
241
242 return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort, tlvs);
243 }
244
245 private static List<HAProxyTLV> readTlvs(final ByteBuf header) {
246 HAProxyTLV haProxyTLV = readNextTLV(header, 0);
247 if (haProxyTLV == null) {
248 return Collections.emptyList();
249 }
250
251 List<HAProxyTLV> haProxyTLVs = new ArrayList<HAProxyTLV>(4);
252
253 try {
254 do {
255 haProxyTLVs.add(haProxyTLV);
256 if (haProxyTLV instanceof HAProxySSLTLV) {
257 haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs());
258 }
259 } while ((haProxyTLV = readNextTLV(header, 0)) != null);
260 } catch (Throwable t) {
261
262 releaseTlvs(haProxyTLVs);
263 throw t;
264 }
265 return haProxyTLVs;
266 }
267
268 private static void releaseDeep(List<HAProxyTLV> children) {
269 for (HAProxyTLV child : children) {
270 child.release();
271 if (child instanceof HAProxySSLTLV) {
272 releaseDeep(((HAProxySSLTLV) child).encapsulatedTLVs());
273 }
274 }
275 }
276
277 private static void releaseTlvs(List<HAProxyTLV> tlvs) {
278 int skip = 0;
279 for (HAProxyTLV tlv : tlvs) {
280 if (skip > 0) {
281 skip--;
282
283
284 if (tlv instanceof HAProxySSLTLV) {
285 releaseDeep(((HAProxySSLTLV) tlv).encapsulatedTLVs());
286 }
287 } else if (tlv instanceof HAProxySSLTLV) {
288
289
290
291 skip = ((HAProxySSLTLV) tlv).encapsulatedTLVs().size();
292 }
293 tlv.release();
294 }
295 }
296
297 private static HAProxyTLV readNextTLV(final ByteBuf header, int nestingLevel) {
298 if (nestingLevel > MAX_NESTING_LEVEL) {
299 throw new HAProxyProtocolException(
300 "Maximum TLV nesting level reached: " + nestingLevel + " (expected: < " + MAX_NESTING_LEVEL + ')');
301 }
302
303 if (header.readableBytes() < 4) {
304 return null;
305 }
306
307 final byte typeAsByte = header.readByte();
308 final HAProxyTLV.Type type = HAProxyTLV.Type.typeForByteValue(typeAsByte);
309
310 final int length = header.readUnsignedShort();
311 switch (type) {
312 case PP2_TYPE_SSL:
313 if (length < 5) {
314 throw new HAProxyProtocolException("TLV length must be at least 5 but was: " + length);
315 }
316 if (length > header.readableBytes()) {
317 throw new HAProxyProtocolException("TLV length must be smaller or equal the readable bytes (" +
318 header.readableBytes() + ") but was: " + length);
319 }
320
321
322 final ByteBuf rawContent = header.slice(header.readerIndex(), length);
323 final ByteBuf byteBuf = header.readSlice(length);
324 final byte client = byteBuf.readByte();
325 final int verify = byteBuf.readInt();
326
327 if (byteBuf.readableBytes() >= 4) {
328
329 final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4);
330 try {
331 do {
332 final HAProxyTLV haProxyTLV = readNextTLV(byteBuf, nestingLevel + 1);
333 if (haProxyTLV == null) {
334 break;
335 }
336 encapsulatedTlvs.add(haProxyTLV);
337 } while (byteBuf.readableBytes() >= 4);
338 } catch (Throwable t) {
339
340 releaseTlvs(encapsulatedTlvs);
341 throw t;
342 }
343
344 return new HAProxySSLTLV(verify, client, encapsulatedTlvs, rawContent.retain());
345 }
346 return new HAProxySSLTLV(verify, client, Collections.<HAProxyTLV>emptyList(), rawContent.retain());
347
348 case PP2_TYPE_ALPN:
349 case PP2_TYPE_AUTHORITY:
350 case PP2_TYPE_SSL_VERSION:
351 case PP2_TYPE_SSL_CN:
352 case PP2_TYPE_NETNS:
353 case OTHER:
354 return new HAProxyTLV(type, typeAsByte, header.readRetainedSlice(length));
355 default:
356 return null;
357 }
358 }
359
360
361
362
363
364
365
366
367 static HAProxyMessage decodeHeader(String header) {
368 if (header == null) {
369 throw new HAProxyProtocolException("header");
370 }
371
372 String[] parts = header.split(" ");
373 int numParts = parts.length;
374
375 if (numParts < 2) {
376 throw new HAProxyProtocolException(
377 "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
378 }
379
380 if (!"PROXY".equals(parts[0])) {
381 throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
382 }
383
384 HAProxyProxiedProtocol protAndFam;
385 try {
386 protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]);
387 } catch (IllegalArgumentException e) {
388 throw new HAProxyProtocolException(e);
389 }
390
391 if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
392 protAndFam != HAProxyProxiedProtocol.TCP6 &&
393 protAndFam != HAProxyProxiedProtocol.UNKNOWN) {
394 throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
395 }
396
397 if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
398 return unknownMsg(HAProxyProtocolVersion.V1, HAProxyCommand.PROXY);
399 }
400
401 if (numParts != 6) {
402 throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
403 }
404
405 try {
406 return new HAProxyMessage(
407 HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
408 protAndFam, parts[2], parts[3], parts[4], parts[5]);
409 } catch (RuntimeException e) {
410 throw new HAProxyProtocolException("invalid HAProxy message", e);
411 }
412 }
413
414
415
416
417
418 private static HAProxyMessage unknownMsg(HAProxyProtocolVersion version, HAProxyCommand command) {
419 return new HAProxyMessage(version, command, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
420 }
421
422
423
424
425
426
427
428
429 private static String ipBytesToString(ByteBuf header, int addressLen) {
430 StringBuilder sb = new StringBuilder();
431 final int ipv4Len = 4;
432 final int ipv6Len = 8;
433 if (addressLen == ipv4Len) {
434 for (int i = 0; i < ipv4Len; i++) {
435 sb.append(header.readByte() & 0xff);
436 sb.append('.');
437 }
438 } else {
439 for (int i = 0; i < ipv6Len; i++) {
440 sb.append(Integer.toHexString(header.readUnsignedShort()));
441 sb.append(':');
442 }
443 }
444 sb.setLength(sb.length() - 1);
445 return sb.toString();
446 }
447
448
449
450
451
452
453
454
455 private static int portStringToInt(String value) {
456 int port;
457 try {
458 port = Integer.parseInt(value);
459 } catch (NumberFormatException e) {
460 throw new IllegalArgumentException("invalid port: " + value, e);
461 }
462
463 if (port <= 0 || port > 65535) {
464 throw new IllegalArgumentException("invalid port: " + value + " (expected: 1 ~ 65535)");
465 }
466
467 return port;
468 }
469
470
471
472
473
474
475
476
477 private static void checkAddress(String address, AddressFamily addrFamily) {
478 ObjectUtil.checkNotNull(addrFamily, "addrFamily");
479
480 switch (addrFamily) {
481 case AF_UNSPEC:
482 if (address != null) {
483 throw new IllegalArgumentException("unable to validate an AF_UNSPEC address: " + address);
484 }
485 return;
486 case AF_UNIX:
487 ObjectUtil.checkNotNull(address, "address");
488 if (address.getBytes(CharsetUtil.US_ASCII).length > 108) {
489 throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
490 }
491 return;
492 }
493
494 ObjectUtil.checkNotNull(address, "address");
495
496 switch (addrFamily) {
497 case AF_IPv4:
498 if (!NetUtil.isValidIpV4Address(address)) {
499 throw new IllegalArgumentException("invalid IPv4 address: " + address);
500 }
501 break;
502 case AF_IPv6:
503 if (!NetUtil.isValidIpV6Address(address)) {
504 throw new IllegalArgumentException("invalid IPv6 address: " + address);
505 }
506 break;
507 default:
508 throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
509 }
510 }
511
512
513
514
515
516
517
518 private static void checkPort(int port, AddressFamily addrFamily) {
519 switch (addrFamily) {
520 case AF_IPv6:
521 case AF_IPv4:
522 if (port < 0 || port > 65535) {
523 throw new IllegalArgumentException("invalid port: " + port + " (expected: 0 ~ 65535)");
524 }
525 break;
526 case AF_UNIX:
527 case AF_UNSPEC:
528 if (port != 0) {
529 throw new IllegalArgumentException("port cannot be specified with addrFamily: " + addrFamily);
530 }
531 break;
532 default:
533 throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
534 }
535 }
536
537
538
539
540 public HAProxyProtocolVersion protocolVersion() {
541 return protocolVersion;
542 }
543
544
545
546
547 public HAProxyCommand command() {
548 return command;
549 }
550
551
552
553
554 public HAProxyProxiedProtocol proxiedProtocol() {
555 return proxiedProtocol;
556 }
557
558
559
560
561
562 public String sourceAddress() {
563 return sourceAddress;
564 }
565
566
567
568
569 public String destinationAddress() {
570 return destinationAddress;
571 }
572
573
574
575
576 public int sourcePort() {
577 return sourcePort;
578 }
579
580
581
582
583 public int destinationPort() {
584 return destinationPort;
585 }
586
587
588
589
590
591
592 public List<HAProxyTLV> tlvs() {
593 return tlvs;
594 }
595
596 int tlvNumBytes() {
597 int tlvNumBytes = 0;
598 for (int i = 0; i < tlvs.size(); i++) {
599 tlvNumBytes += tlvs.get(i).totalNumBytes();
600 }
601 return tlvNumBytes;
602 }
603
604 @Override
605 public HAProxyMessage touch() {
606 tryRecord();
607 return (HAProxyMessage) super.touch();
608 }
609
610 @Override
611 public HAProxyMessage touch(Object hint) {
612 if (leak != null) {
613 leak.record(hint);
614 }
615 return this;
616 }
617
618 @Override
619 public HAProxyMessage retain() {
620 tryRecord();
621 return (HAProxyMessage) super.retain();
622 }
623
624 @Override
625 public HAProxyMessage retain(int increment) {
626 tryRecord();
627 return (HAProxyMessage) super.retain(increment);
628 }
629
630 @Override
631 public boolean release() {
632 tryRecord();
633 return super.release();
634 }
635
636 @Override
637 public boolean release(int decrement) {
638 tryRecord();
639 return super.release(decrement);
640 }
641
642 private void tryRecord() {
643 if (leak != null) {
644 leak.record();
645 }
646 }
647
648 @Override
649 protected void deallocate() {
650 try {
651 releaseTlvs(tlvs);
652 } finally {
653 final ResourceLeakTracker<HAProxyMessage> leak = this.leak;
654 if (leak != null) {
655 boolean closed = leak.close(this);
656 assert closed;
657 }
658 }
659 }
660
661 @Override
662 public String toString() {
663 StringBuilder sb = new StringBuilder(256)
664 .append(StringUtil.simpleClassName(this))
665 .append("(protocolVersion: ").append(protocolVersion)
666 .append(", command: ").append(command)
667 .append(", proxiedProtocol: ").append(proxiedProtocol)
668 .append(", sourceAddress: ").append(sourceAddress)
669 .append(", destinationAddress: ").append(destinationAddress)
670 .append(", sourcePort: ").append(sourcePort)
671 .append(", destinationPort: ").append(destinationPort)
672 .append(", tlvs: [");
673 if (!tlvs.isEmpty()) {
674 for (HAProxyTLV tlv: tlvs) {
675 sb.append(tlv).append(", ");
676 }
677 sb.setLength(sb.length() - 2);
678 }
679 sb.append("])");
680 return sb.toString();
681 }
682 }