Custom DNS Server with netty - coding the Answer section - java

I am trying to write a little custom DNS server using netty. The following code works but I have two questions:
Is there a better way than using DefaultDnsRawRecord to create the DNS answer section? There are some other DNS records like DefaultDnsOptEcsRecord or DefaultDnsPtrRecord but they seem to be used for other sections of the DNS response.
Should I use the netty internal method io.netty.util.internal.SocketUtils.addressByName() to create a InetAdress object for a fixed IP address without acutally querying the DNS which is required in my case? Is there a standard Java way for this?
Setting up the pipeline:
bootstrap.group(group)
.channel(NioDatagramChannel.class)
.handler(new ChannelInitializer<NioDatagramChannel>() {
#Override
protected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {
nioDatagramChannel.pipeline().addLast(new DatagramDnsQueryDecoder());
nioDatagramChannel.pipeline().addLast(new DatagramDnsResponseEncoder());
nioDatagramChannel.pipeline().addLast(new DnsHandler());
}
})
.option(ChannelOption.SO_BROADCAST, true)
.localAddress("127.0.0.1", 40053);
The DnsHandler:
public class DnsHandler extends SimpleChannelInboundHandler<DatagramDnsQuery> {
#Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramDnsQuery query) throws Exception {
DatagramDnsResponse response = new DatagramDnsResponse(query.recipient(), query.sender(), query.id());
DefaultDnsQuestion dnsQuestion = query.recordAt(DnsSection.QUESTION);
response.addRecord(DnsSection.QUESTION, dnsQuestion);
byte[] address = SocketUtils.addressByName("127.0.0.1").getAddress();
DefaultDnsRawRecord queryAnswer = new DefaultDnsRawRecord(dnsQuestion.name(),
DnsRecordType.A, 3600, Unpooled.wrappedBuffer(address));
response.addRecord(DnsSection.ANSWER, queryAnswer);
ctx.writeAndFlush(response);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
}
}

As netty itself not really has any specify encoder / decoder for server side the DefaultDnsRawRecord may be your best bet for now.
You should not use classes that resist in the .internal. packages as these are for internal usage by netty and may be changed or removed at any time (as these are not considered to be public api). Only use these if you really know what you are doing and not care about breakage.

Related

Netty convert HttpRequest to ByteBuf

In my application I need to receive a byte array on a socket, parse it as a HttpRequest to perform some check and, if the checks passes, get back to the byte array and do some more work.
The application is based on NETTY (this is a requirement).
My first idea was to create a pipeline like this:
HttpRequestDecoder (decode from ByteBuf to HttpRequest)
MyHttpRequestHandler (do my own checks on the HttpRequest)
HttpRequestEncoder (encode the HttpRequest to a ByteBuf)
MyButeBufHandler (do my works with the ByteBuf)
However the HttpRequestEncoder extends the ChannelOutboundHandlerAdapter so it doesn't get called for the inbound data.
How can I accomplish this task?
It would be nice to avoid decoding and re-encoding the request.
Regards,
Massimiliano
Use an EmbeddedChannel in MyHttpRequestHandler.
EmbeddedChannel ch = new EmbeddedChannel(new HttpRequestEncoder());
ch.writeOutbound(msg);
ByteBuf encoded = ch.readOutbound();
You'll have to keep the EmbeddedChannel as a member variable of MyHttpRequestEncoder because HttpRequestEncoder is stateful. Also, please close the EmbeddedChannel when you finished using it (probably in your channelInactive() method.)
I just had to encode and decode some HttpObjects and struggled a bit with it.
The hint that the decoder/encoder are stateful is very valuable.
That's why I thought I'll add my findings here. Maybe it's helpful to someone else.
I declared an RequestEncoder and a ResponseDecoder as a class member, but it still didn't work correctly. Until I remembered that the specific handler I was using the en/decoders within was shared...
That's how I got it to work in the end. My sequenceNr is to distinct between the different requests. I create one encoder and one decoder per request and save them in a HashMap. With my sequenceNr, I'm able to always get the same decoder/encoder for the same request. Don't forget to close and remove the de/encoder channels from the Map after processing the LastContent object.
#ChannelHandler.Sharable
public class HttpTunnelingServerHandler extends ChannelDuplexHandler {
private final Map<Integer, EmbeddedChannel> decoders = Collections.synchronizedMap(new HashMap<Integer, EmbeddedChannel>());
private final Map<Integer, EmbeddedChannel> encoders = Collections.synchronizedMap(new HashMap<Integer, EmbeddedChannel>());
.
.
//Encoding
if (!encoders.containsKey(currentResponse.getSequenceNr())) {
encoders.put(currentResponse.getSequenceNr(), new EmbeddedChannel(new HttpResponseEncoder()));
}
EmbeddedChannel encoderChannel = encoders.get(currentResponse.getSequenceNr());
encoderChannel.writeOutbound(recievedHttpObject);
ByteBuf encoded = (ByteBuf) encoderChannel.readOutbound();
.
.
//Decoding
if (!decoders.containsKey(sequenceNr)) {
decoders.put(sequenceNr, new EmbeddedChannel(new HttpRequestDecoder()));
}
EmbeddedChannel decoderChannel = decoders.get(sequenceNr);
decoderChannel.writeInbound(bb);
HttpObject httpObject = (HttpObject) decoderChannel.readInbound();
}
How about the put the EmbeddedChannel as the handler channel's attribute, instead of HashMap. Isn't it the same what you claim to solve the stateful encoder/decoder?
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(EMBEDED_CH).set( new EmbeddedChannel(new HttpRequestDecoder()));
super.channelActive(ctx);
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
EmbeddedChannel embedCh = ctx.channel().attr(EMBEDED_CH).get();
if (embedCh != null) {
embedCh.close();
}
super.channelInactive(ctx);
}

apache tomcat 8 websocket origin and client address

H.e.l.l.o community, i hope someone can help me ... i am using apache tomcat 8.0.0-RC5 and JSR-356 web socket API ...
I have 2 questions:
1) Is it possible to get the client ip on #OnOpen method ??
2) Is it possible to get the origin of the connection ???
I followed the websocket example which comes with the distribution of tomcat and i was not able to find the answers .... My java class is basically as follow
#ServerEndpoint(value = "/data.socket")
public class MyWebSocket {
#OnOpen
public void onOpen(Session session) {
// Here is where i need the origin and remote client address
}
#OnClose
public void onClose() {
// disconnection handling
}
#OnMessage
public void onMessage(String message) {
// message handling
}
#OnError
public void onError(Session session, Throwable throwable) {
// Error handling
}
}
Repeating the answer I already gave you on the Tomcat users mailing list...
Client IP. No. Generally this type of information is available at the handshake
which occurs before OnOpen but client IP is not one of the pieces of
information exposed. You might be better blocking these earlier e.g. with iptables or similar.
Origin. ServerEndpointConfig.Configurator.checkOrigin(String) You'll need a custom Configurator. Keep in mind that a malicious client can forge the origin header.
I know this question is old, but just in case someone else finds it in a web search:
Yes there is an easy workaround. A Servlet can receive and forward a WebSocket upgrade request. The trick is to get the client IP address and expose it as a parameter.
Here's your servlet code:
#WebServlet("/myExternalEntryPoint")
public class WebSocketServlet extends HttpServlet {
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
var dispatcher = getServletContext().getRequestDispatcher("/myInternalEntryPoint");
var requestWrapper = new MyRequestWrapper(request);
dispatcher.forward(requestWrapper, response);
}
}
And here's MyRequestWrapper:
class MyRequestWrapper extends HttpServletRequestWrapper {
public RequestWrapper(HttpServletRequest request) {
super(request);
}
public Map<String, String[]> getParameterMap() {
return Collections.singletonMap("remoteAddr", new String[] {getRequest().getRemoteAddr()});
}
}
Now in your WebSocket implementation, you'll be able to get remoteAddr via javax.websocket.Session.getRequestParameterMap().
Naturally, if your original request has parameters that you care about, you'll need to create a map that includes those as well. Also I recommend you append a separate, secret parameter and check for it in your WebSocket code to prevent anyone from hitting the internal entry point directly.
I figured out this was possible because of this thoughtful comment in the Tomcat source code (WsFilter.java):
// No endpoint registered for the requested path. Let the
// application handle it (it might redirect or forward for example)

Decoding GET and POST methods with Netty

I need to create a server application with Netty that will receive requests both like "GETs" or "POSTs". In case of GET requests, the parameters would come as query parameters.
I have been checking that HttpRequestDecoder would be suitable for the GET requests, and HttpPostRequestDecoder for the post. But how could I handle both at the same time?
Not very familiar with Netty, so I would appretiate a little bit of help :)
The netty provisions us to handle a request as a pipeline where you define the pipeline as a sequence of handlers.
One sequence could be like this:
p.addLast ("codec", new HttpServerCodec ());
p.addLast ("handler", new YourHandler());
where p is an instance of ChannelPipeline interface. You can define the YourHandler class as follows:
public class YourHandler extends ChannelInboundHandlerAdapter
{
#Override
public void channelRead (ChannelHandlerContext channelHandlerCtxt, Object msg)
throws Exception
{
// Handle requests as switch cases. GET, POST,...
// This post helps you to understanding switch case usage on strings:
// http://stackoverflow.com/questions/338206/switch-statement-with-strings-in-java
if (msg instanceof FullHttpRequest)
{
FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
switch (fullHttpRequest.getMethod ().toString ())
{
case "GET":
case "POST":
...
}
}
}
}
You want to first check the request type and switch on the value (GET/POST/PUT/DELETE etc...)
http://docs.jboss.org/netty/3.1/api/org/jboss/netty/handler/codec/http/HttpMethod.html

Netty UDP compatible decoders

Which decoders are safe to extend in use with a Non Blocking Datagram Channel?
Essentially, I need to go from *ByteBuff to String, which I then have code that will turn that string into an object. Also, this would need to be accomplished with a decoder. From object to string and finally back to a *ByteBuff.
I have tried extending ByteToMessageDecoder, but it seems that Netty never invokes the decode method. So I am not sure if this is mainly a problem with the Datagram Channel or a problem with my principle understanding of decoders...
Just in case here is some of my code
Initializer:
public class Initializer extends ChannelInitializer<NioDatagramChannel> {
private SimpleChannelInboundHandler<Packet> sipHandler;
public Initializer(SimpleChannelInboundHandler<Packet> handler) {
sipHandler = handler;
}
#Override
protected void initChannel(NioDatagramChannel chan) throws Exception {
ChannelPipeline pipe = chan.pipeline();
pipe.addLast("decoder", new SipDecoder());
pipe.addLast("handler", sipHandler);
pipe.addLast("encoder", new SipEncoder());
}
}
Beginning of my Decoder:
public class SipDecoder extends ByteToMessageDecoder {
private Packet sip;
#Override
protected void decode(ChannelHandlerContext context, ByteBuf byteBuf, List<Object> objects) throws Exception {
System.out.println("got hit...");
String data = new String(byteBuf.array());
sip = new Packet();
// [...]
}
}
To handle DatagramPacket's you need to use MessageToMessageDecoder as ByteToMessageDecoder only works for ByteBuf.

Apache Camel: Is there any way to set a message to fault without injecting the exchange in a bean method?

I'm totally new to Apache Camel, after finished the "Camel in Action", I am still not so clear about a basic function. Is there any way the set a message to be a fault message without injecting the exchange in bean method.
Here I can provide a example:
class HttpBean{
public void parseIP(#Properties Map properties,#XPath("//ip") String ip){
properties.put("IP", ip);
}
}
A method named "parseIP" is designed to parse ip in the body using xpath and save its value in a property map. But if there's no ip tag in body at all, I want to create a fault message and terminate the process(Not just throw a exception which will be treated as a recoverable error, Here I want a unrecoverable error). To achieve this goal, I can use the following code:
class HttpBean{
public void parseIP(#Properties Map properties,#XPath("//ip") String ip, Exchange exchange){
if(ip == null){
exchange.getIn().setFault(true);
exchange.getIn().setBody("Ip is missing");
}
properties.put("IP", ip);
}
}
But is this the best solution? Because once I inject an exchange into a bean method, I think it's not different than a camel processor and I lose most of its advantage. A bean in camel can finish its own work without using any Camel specific API, but once the exchange is injected, the advantage is gone.
Someone can help me with the question? Thanks a lot.
just set a fault from the route based on the results of the bean...if property IP is null, then set fault, etc...
#Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
#Override
public void configure() throws Exception {
from("direct:route1")
.bean(new HttpBean())
.filter(property("IP").isNull()).setFaultBody(constant("Ip is missing")).end()
.to("mock:mock");
}
};
}
public static class HttpBean {
public void parseIP(#Properties Map properties, #XPath("//ip") String ip){
if(ip != null && ip.length() > 0){
properties.put("IP", ip);
}
}
}
If anyone runs across this question and are looking for details on how to set Soap Faults from CXF in POJO, Message, or Payload mode, take a look at this link from Talend:
https://help.talend.com/display/TalendESBMediationDeveloperGuide54EN/3.8.10+How+to+throw+a+SOAP+Fault+from+Camel
Here is a code snippet:
SoapFault fault = new SoapFault("unable to process request", SoapFault.FAULT_CODE_SERVER);
Element detail = fault.getOrCreateDetail();
Document detailPayload = getYourDetailHere();
detail.appendChild(detail.getOwnerDocument().importNode(detailPayload.getDocumentElement(), true));
Message outMessage = exchange.getOut();
outMessage.setHeader(org.apache.cxf.message.Message.RESPONSE_CODE, new Integer(500));
outMessage.setFault(true);
outMessage.setBody(fault);

Categories