View Javadoc
1   /*
2    * Copyright 2014 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  
17  package io.netty.handler.proxy;
18  
19  import io.netty.channel.ChannelHandlerContext;
20  import io.netty.channel.ChannelPipeline;
21  import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandRequest;
22  import io.netty.handler.codec.socksx.v4.Socks4ClientDecoder;
23  import io.netty.handler.codec.socksx.v4.Socks4ClientEncoder;
24  import io.netty.handler.codec.socksx.v4.Socks4CommandResponse;
25  import io.netty.handler.codec.socksx.v4.Socks4CommandStatus;
26  import io.netty.handler.codec.socksx.v4.Socks4CommandType;
27  
28  import java.net.InetSocketAddress;
29  import java.net.SocketAddress;
30  
31  /**
32   * Handler that establishes a blind forwarding proxy tunnel using
33   * <a href="https://www.openssh.com/txt/socks4.protocol">SOCKS4</a> protocol.
34   */
35  public final class Socks4ProxyHandler extends ProxyHandler {
36  
37      private static final String PROTOCOL = "socks4";
38      private static final String AUTH_USERNAME = "username";
39  
40      private final String username;
41  
42      private String decoderName;
43      private String encoderName;
44  
45      public Socks4ProxyHandler(SocketAddress proxyAddress) {
46          this(proxyAddress, null);
47      }
48  
49      public Socks4ProxyHandler(SocketAddress proxyAddress, String username) {
50          super(proxyAddress);
51          if (username != null && username.isEmpty()) {
52              username = null;
53          }
54          this.username = username;
55      }
56  
57      @Override
58      public String protocol() {
59          return PROTOCOL;
60      }
61  
62      @Override
63      public String authScheme() {
64          return username != null? AUTH_USERNAME : AUTH_NONE;
65      }
66  
67      public String username() {
68          return username;
69      }
70  
71      @Override
72      protected void addCodec(ChannelHandlerContext ctx) throws Exception {
73          ChannelPipeline p = ctx.pipeline();
74          String name = ctx.name();
75  
76          Socks4ClientDecoder decoder = new Socks4ClientDecoder();
77          p.addBefore(name, null, decoder);
78  
79          decoderName = p.context(decoder).name();
80          encoderName = decoderName + ".encoder";
81  
82          p.addBefore(name, encoderName, Socks4ClientEncoder.INSTANCE);
83      }
84  
85      @Override
86      protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
87          ChannelPipeline p = ctx.pipeline();
88          p.remove(encoderName);
89      }
90  
91      @Override
92      protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
93          ChannelPipeline p = ctx.pipeline();
94          p.remove(decoderName);
95      }
96  
97      @Override
98      protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception {
99          InetSocketAddress raddr = destinationAddress();
100         String rhost;
101         if (raddr.isUnresolved()) {
102             rhost = raddr.getHostString();
103         } else {
104             rhost = raddr.getAddress().getHostAddress();
105         }
106         return new DefaultSocks4CommandRequest(
107                 Socks4CommandType.CONNECT, rhost, raddr.getPort(), username != null? username : "");
108     }
109 
110     @Override
111     protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
112         final Socks4CommandResponse res = (Socks4CommandResponse) response;
113         final Socks4CommandStatus status = res.status();
114         if (status == Socks4CommandStatus.SUCCESS) {
115             return true;
116         }
117 
118         throw new ProxyConnectException(exceptionMessage("status: " + status));
119     }
120 }