1 /*
2 * Copyright 2012 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.socks;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.ByteBufUtil;
20 import io.netty.util.CharsetUtil;
21 import io.netty.util.NetUtil;
22 import io.netty.util.internal.ObjectUtil;
23
24 import java.net.IDN;
25
26 /**
27 * A socks cmd response.
28 *
29 * @see SocksCmdRequest
30 * @see SocksCmdResponseDecoder
31 */
32 public final class SocksCmdResponse extends SocksResponse {
33 private final SocksCmdStatus cmdStatus;
34
35 private final SocksAddressType addressType;
36 private final String host;
37 private final int port;
38
39 // All arrays are initialized on construction time to 0/false/null remove array Initialization
40 private static final byte[] DOMAIN_ZEROED = {0x00};
41 private static final byte[] IPv4_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00};
42 private static final byte[] IPv6_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00,
43 0x00, 0x00, 0x00, 0x00,
44 0x00, 0x00, 0x00, 0x00,
45 0x00, 0x00, 0x00, 0x00};
46
47 public SocksCmdResponse(SocksCmdStatus cmdStatus, SocksAddressType addressType) {
48 this(cmdStatus, addressType, null, 0);
49 }
50
51 /**
52 * Constructs new response and includes provided host and port as part of it.
53 *
54 * @param cmdStatus status of the response
55 * @param addressType type of host parameter
56 * @param host host (BND.ADDR field) is address that server used when connecting to the target host.
57 * When null a value of 4/8 0x00 octets will be used for IPv4/IPv6 and a single 0x00 byte will be
58 * used for domain addressType. Value is converted to ASCII using {@link IDN#toASCII(String)}.
59 * @param port port (BND.PORT field) that the server assigned to connect to the target host
60 * @throws NullPointerException in case cmdStatus or addressType are missing
61 * @throws IllegalArgumentException in case host or port cannot be validated
62 * @see IDN#toASCII(String)
63 */
64 public SocksCmdResponse(SocksCmdStatus cmdStatus, SocksAddressType addressType, String host, int port) {
65 super(SocksResponseType.CMD);
66 ObjectUtil.checkNotNull(cmdStatus, "cmdStatus");
67 ObjectUtil.checkNotNull(addressType, "addressType");
68 if (host != null) {
69 switch (addressType) {
70 case IPv4:
71 if (!NetUtil.isValidIpV4Address(host)) {
72 throw new IllegalArgumentException(host + " is not a valid IPv4 address");
73 }
74 break;
75 case DOMAIN:
76 String asciiHost = IDN.toASCII(host);
77 if (asciiHost.length() > 255) {
78 throw new IllegalArgumentException(host + " IDN: " + asciiHost + " exceeds 255 char limit");
79 }
80 host = asciiHost;
81 break;
82 case IPv6:
83 if (!NetUtil.isValidIpV6Address(host)) {
84 throw new IllegalArgumentException(host + " is not a valid IPv6 address");
85 }
86 break;
87 case UNKNOWN:
88 break;
89 }
90 }
91 if (port < 0 || port > 65535) {
92 throw new IllegalArgumentException(port + " is not in bounds 0 <= x <= 65535");
93 }
94 this.cmdStatus = cmdStatus;
95 this.addressType = addressType;
96 this.host = host;
97 this.port = port;
98 }
99
100 /**
101 * Returns the {@link SocksCmdStatus} of this {@link SocksCmdResponse}
102 *
103 * @return The {@link SocksCmdStatus} of this {@link SocksCmdResponse}
104 */
105 public SocksCmdStatus cmdStatus() {
106 return cmdStatus;
107 }
108
109 /**
110 * Returns the {@link SocksAddressType} of this {@link SocksCmdResponse}
111 *
112 * @return The {@link SocksAddressType} of this {@link SocksCmdResponse}
113 */
114 public SocksAddressType addressType() {
115 return addressType;
116 }
117
118 /**
119 * Returns host that is used as a parameter in {@link SocksCmdType}.
120 * Host (BND.ADDR field in response) is address that server used when connecting to the target host.
121 * This is typically different from address which client uses to connect to the SOCKS server.
122 *
123 * @return host that is used as a parameter in {@link SocksCmdType}
124 * or null when there was no host specified during response construction
125 */
126 public String host() {
127 return host != null && addressType == SocksAddressType.DOMAIN ? IDN.toUnicode(host) : host;
128 }
129
130 /**
131 * Returns port that is used as a parameter in {@link SocksCmdType}.
132 * Port (BND.PORT field in response) is port that the server assigned to connect to the target host.
133 *
134 * @return port that is used as a parameter in {@link SocksCmdType}
135 */
136 public int port() {
137 return port;
138 }
139
140 @Override
141 public void encodeAsByteBuf(ByteBuf byteBuf) {
142 byteBuf.writeByte(protocolVersion().byteValue());
143 byteBuf.writeByte(cmdStatus.byteValue());
144 byteBuf.writeByte(0x00);
145 byteBuf.writeByte(addressType.byteValue());
146 switch (addressType) {
147 case IPv4: {
148 byte[] hostContent = host == null ?
149 IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
150 byteBuf.writeBytes(hostContent);
151 ByteBufUtil.writeShortBE(byteBuf, port);
152 break;
153 }
154 case DOMAIN: {
155 if (host != null) {
156 byteBuf.writeByte(host.length());
157 byteBuf.writeCharSequence(host, CharsetUtil.US_ASCII);
158 } else {
159 byteBuf.writeByte(DOMAIN_ZEROED.length);
160 byteBuf.writeBytes(DOMAIN_ZEROED);
161 }
162 ByteBufUtil.writeShortBE(byteBuf, port);
163 break;
164 }
165 case IPv6: {
166 byte[] hostContent = host == null
167 ? IPv6_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host);
168 byteBuf.writeBytes(hostContent);
169 ByteBufUtil.writeShortBE(byteBuf, port);
170 break;
171 }
172 }
173 }
174 }