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, protocolVersion);
104 checkAddress(destinationAddress, addrFamily, protocolVersion);
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
478 private static void checkAddress(String address, AddressFamily addrFamily, HAProxyProtocolVersion version) {
479 ObjectUtil.checkNotNull(addrFamily, "addrFamily");
480
481 switch (addrFamily) {
482 case AF_UNSPEC:
483 if (address != null) {
484 throw new IllegalArgumentException("unable to validate an AF_UNSPEC address: " + address);
485 }
486 return;
487 case AF_UNIX:
488 ObjectUtil.checkNotNull(address, "address");
489 if (address.getBytes(CharsetUtil.US_ASCII).length > 108) {
490 throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
491 }
492 if (version == HAProxyProtocolVersion.V1) {
493
494 for (int i = 0, len = address.length(); i < len; i++) {
495 char c = address.charAt(i);
496 if (c == '\r' || c == '\n' || c == ' ') {
497 throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
498 }
499 }
500 }
501 return;
502 }
503
504 ObjectUtil.checkNotNull(address, "address");
505
506 switch (addrFamily) {
507 case AF_IPv4:
508 if (!NetUtil.isValidIpV4Address(address)) {
509 throw new IllegalArgumentException("invalid IPv4 address: " + address);
510 }
511 break;
512 case AF_IPv6:
513 if (!NetUtil.isValidIpV6Address(address)) {
514 throw new IllegalArgumentException("invalid IPv6 address: " + address);
515 }
516 break;
517 default:
518 throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
519 }
520 }
521
522
523
524
525
526
527
528 private static void checkPort(int port, AddressFamily addrFamily) {
529 switch (addrFamily) {
530 case AF_IPv6:
531 case AF_IPv4:
532 if (port < 0 || port > 65535) {
533 throw new IllegalArgumentException("invalid port: " + port + " (expected: 0 ~ 65535)");
534 }
535 break;
536 case AF_UNIX:
537 case AF_UNSPEC:
538 if (port != 0) {
539 throw new IllegalArgumentException("port cannot be specified with addrFamily: " + addrFamily);
540 }
541 break;
542 default:
543 throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
544 }
545 }
546
547
548
549
550 public HAProxyProtocolVersion protocolVersion() {
551 return protocolVersion;
552 }
553
554
555
556
557 public HAProxyCommand command() {
558 return command;
559 }
560
561
562
563
564 public HAProxyProxiedProtocol proxiedProtocol() {
565 return proxiedProtocol;
566 }
567
568
569
570
571
572 public String sourceAddress() {
573 return sourceAddress;
574 }
575
576
577
578
579 public String destinationAddress() {
580 return destinationAddress;
581 }
582
583
584
585
586 public int sourcePort() {
587 return sourcePort;
588 }
589
590
591
592
593 public int destinationPort() {
594 return destinationPort;
595 }
596
597
598
599
600
601
602 public List<HAProxyTLV> tlvs() {
603 return tlvs;
604 }
605
606 int tlvNumBytes() {
607 int tlvNumBytes = 0;
608 for (int i = 0; i < tlvs.size(); i++) {
609 tlvNumBytes += tlvs.get(i).totalNumBytes();
610 }
611 return tlvNumBytes;
612 }
613
614 @Override
615 public HAProxyMessage touch() {
616 tryRecord();
617 return (HAProxyMessage) super.touch();
618 }
619
620 @Override
621 public HAProxyMessage touch(Object hint) {
622 if (leak != null) {
623 leak.record(hint);
624 }
625 return this;
626 }
627
628 @Override
629 public HAProxyMessage retain() {
630 tryRecord();
631 return (HAProxyMessage) super.retain();
632 }
633
634 @Override
635 public HAProxyMessage retain(int increment) {
636 tryRecord();
637 return (HAProxyMessage) super.retain(increment);
638 }
639
640 @Override
641 public boolean release() {
642 tryRecord();
643 return super.release();
644 }
645
646 @Override
647 public boolean release(int decrement) {
648 tryRecord();
649 return super.release(decrement);
650 }
651
652 private void tryRecord() {
653 if (leak != null) {
654 leak.record();
655 }
656 }
657
658 @Override
659 protected void deallocate() {
660 try {
661 releaseTlvs(tlvs);
662 } finally {
663 final ResourceLeakTracker<HAProxyMessage> leak = this.leak;
664 if (leak != null) {
665 boolean closed = leak.close(this);
666 assert closed;
667 }
668 }
669 }
670
671 @Override
672 public String toString() {
673 StringBuilder sb = new StringBuilder(256)
674 .append(StringUtil.simpleClassName(this))
675 .append("(protocolVersion: ").append(protocolVersion)
676 .append(", command: ").append(command)
677 .append(", proxiedProtocol: ").append(proxiedProtocol)
678 .append(", sourceAddress: ").append(sourceAddress)
679 .append(", destinationAddress: ").append(destinationAddress)
680 .append(", sourcePort: ").append(sourcePort)
681 .append(", destinationPort: ").append(destinationPort)
682 .append(", tlvs: [");
683 if (!tlvs.isEmpty()) {
684 for (HAProxyTLV tlv: tlvs) {
685 sb.append(tlv).append(", ");
686 }
687 sb.setLength(sb.length() - 2);
688 }
689 sb.append("])");
690 return sb.toString();
691 }
692 }