Simple STUN client in java
Asked Answered
C

3

6

I have found several java STUN implementations

Java and Which Stun libraries i should use?

There is

See also: STUN, TURN, ICE library for Java

But it is jars with many classes. I wish to find something simple in form of single method or at least single small class. Like following python code.

https://github.com/jtriley/pystun/blob/develop/stun/init.py

Reasonable answer why STUN in Java is so huge is also acceptable.

Chromatid answered 14/12, 2014 at 12:43 Comment(0)
C
9

Reasonable answer why STUN in Java is so huge is also acceptable.

It's a reasonable question. 99% of what STUN is just a simple echo/response protocol for a client to self-discover the IP and port mapping as a result of NAT between it and the public internet. Having built a STUN library in C++, I have some insight.

Let's think about what is required of a STUN library:

  • A message writer that generates the STUN messages with an attribute field schema that not only allows for fields to appear in any order, it also allows for custom attributes to be added as well.

  • A message parser that can read such messages back and convert a data structure reasonable for code to use. It needs to do this securely and avoid unhandled exceptions.

  • Socket networking code to send/receive such messages. And STUN servers are technically required to listen on 2 IPs and 2 ports, so that makes the networking code for the server a bit more complex.

  • If we just care about binding requests and binding responses, we'd be done. But the STUN RFCs also define a set of NAT classification tests. So additional state machine logic is needed to make any such library complete.

  • And if the STUN library is going to go all the way with the security options afforded by the protocol, it would need some amount of crypto code for hashing and signing of messages

So combining all this into a library that anyone can use for all the different purposes of STUN including mapped address discovery, NAT classification, and ICE negotiation, it starts to get big quick.

You could easily just roll some socket code that hardcodes the bytes of a binding request and then some hacked up parsing to parse the response. That might meet your own needs, but a well established open source library would never be written this way.

JSTUN is a good start. I've shared some interop and bug fixing code with the original author. He doesn't actively maintain it, but it's a good implementation of RFC 3489. I even hacked it up once to run on Android.

To generate a STUN binding request in JSTUN.

MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
sendMH.generateTransactionID();

// add an empty ChangeRequest attribute. Not required by the standard, but JSTUN server requires it
ChangeRequest changeRequest = new ChangeRequest();
sendMH.addMessageAttribute(changeRequest);

byte[] data = sendMH.getBytes();

// not shown - sending the message

Then to parse the response back:

byte [] receivedData = new byte[500];

// not shown - socket code that receives the messages into receivedData
receiveMH.parseAttributes(receivedData);
MappedAddress ma = (MappedAddress) receiveMH.getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);    

Then combine the above with some socket code. The best example of combining the above with socket code can be found in the DiscoveryTest.java source file. You really just need the code in the test1() method of this class.

Colocynth answered 20/12, 2014 at 20:48 Comment(0)
D
4

Java:

    MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
    // sendMH.generateTransactionID();

    // add an empty ChangeRequest attribute. Not required by the
    // standard,
    // but JSTUN server requires it

    ChangeRequest changeRequest = new ChangeRequest();
    sendMH.addMessageAttribute(changeRequest);

    byte[] data = sendMH.getBytes();
    

    s = new DatagramSocket();
    s.setReuseAddress(true);

    DatagramPacket p = new DatagramPacket(data, data.length, InetAddress.getByName("stun.l.google.com"), 19302);
    s.send(p);

    DatagramPacket rp;

    rp = new DatagramPacket(new byte[32], 32);

    s.receive(rp);
    MessageHeader receiveMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingResponse);
    // System.out.println(receiveMH.getTransactionID().toString() + "Size:"
    // + receiveMH.getTransactionID().length);
    receiveMH.parseAttributes(rp.getData());
    MappedAddress ma = (MappedAddress) receiveMH
            .getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);
    System.out.println(ma.getAddress()+" "+ma.getPort());

kotlin

val sendMH = MessageHeader(MessageHeaderInterface.MessageHeaderType.BindingRequest)
sendMH.generateTransactionID()
println("sent transaction id: ${sendMH.transactionID.toHexString()}")

// add an empty ChangeRequest attribute. Not required by the standard,
// but JSTUN server requires it
val changeRequest = ChangeRequest()
sendMH.addMessageAttribute(changeRequest)
val data:ByteArray = sendMH.bytes

val s = DatagramSocket()
s.reuseAddress = true
val p = DatagramPacket(data,data.size,InetAddress.getByName("stun.l.google.com"),19302)
s.send(p)

val rp = DatagramPacket(ByteArray(32),32)
s.receive(rp)
val receiveMH = MessageHeader(MessageHeaderInterface.MessageHeaderType.BindingResponse)
try
{
    receiveMH.parseAttributes(rp.data)
}
catch (e:MessageAttributeParsingException)
{
    e.printStackTrace()
}
println("received transaction id: ${receiveMH.transactionID.toHexString()}")
val ma = receiveMH.getMessageAttribute(MessageAttributeInterface.MessageAttributeType.MappedAddress) as MappedAddress
println("${ma.address}:${ma.port}")
Dorman answered 2/5, 2017 at 18:32 Comment(0)
M
2
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import de.javawi.jstun.attribute.ChangeRequest;
import de.javawi.jstun.attribute.ChangedAddress;
import de.javawi.jstun.attribute.ErrorCode;
import de.javawi.jstun.attribute.MappedAddress;
import de.javawi.jstun.attribute.MessageAttribute;
import de.javawi.jstun.attribute.MessageAttributeException;
import de.javawi.jstun.attribute.MessageAttributeParsingException;
import de.javawi.jstun.header.MessageHeader;
import de.javawi.jstun.header.MessageHeaderParsingException;
import de.javawi.jstun.util.UtilityException;


public class StunTest { public static void main(String[] args) throws UtilityException, IOException {


        MessageHeader sendMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingRequest);
        // sendMH.generateTransactionID();

        // add an empty ChangeRequest attribute. Not required by the
        // standard,
        // but JSTUN server requires it

        ChangeRequest changeRequest = new ChangeRequest();
        sendMH.addMessageAttribute(changeRequest);

        byte[] data = sendMH.getBytes();


        DatagramSocket s = new DatagramSocket();
        s.setReuseAddress(true);

        DatagramPacket p = new DatagramPacket(data, data.length, InetAddress.getByName("stun.l.google.com"), 19302);
        s.send(p);

        DatagramPacket rp;

        rp = new DatagramPacket(new byte[32], 32);

        s.receive(rp);
        MessageHeader receiveMH = new MessageHeader(MessageHeader.MessageHeaderType.BindingResponse);
        // System.out.println(receiveMH.getTransactionID().toString() + "Size:"
        // + receiveMH.getTransactionID().length);
        try {
                receiveMH.parseAttributes(rp.getData());
        } catch (MessageAttributeParsingException e) {
                e.printStackTrace();
        }
        MappedAddress ma = (MappedAddress) receiveMH
                .getMessageAttribute(MessageAttribute.MessageAttributeType.MappedAddress);
        System.out.println(ma.getAddress()+" "+ma.getPort());
    }
}
Mauritius answered 5/8, 2019 at 0:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.