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