@Override
  public void connect() {
    if (connectionState.get() == ConnectionState.CONNECTED) {
      return;
    }

    stunStack = applicationContext.getBean(StunStack.class);

    serverAddress = new TransportAddress(turnHost, turnPort, Transport.UDP);
    connectionState.set(ConnectionState.CONNECTING);
    try {
      localSocket = new MultiplexingDatagramSocket(0, getLocalHost());
      channelDataSocket =
          localSocket.getSocket(
              new TurnDatagramPacketFilter(serverAddress) {
                @Override
                public boolean accept(DatagramPacket packet) {
                  return isChannelData(packet);
                }

                @Override
                protected boolean acceptMethod(char method) {
                  return false;
                }
              });

      localAddress =
          new TransportAddress(
              (InetSocketAddress) localSocket.getLocalSocketAddress(), Transport.UDP);
      stunStack.addSocket(new IceUdpSocketWrapper(localSocket), serverAddress);

      stunStack.addIndicationListener(localAddress, this::onIndication);

      releaseAllocation();
      allocateAddress(serverAddress);
      permit(connectivityService.getExternalSocketAddress());

      connectionState.set(ConnectionState.CONNECTED);
      scheduledExecutorService.execute(this::runInReceiveChannelDataThread);
    } catch (StunException | IOException e) {
      throw new RuntimeException(e);
    }
  }
 @VisibleForTesting
 public InetSocketAddress getLocalSocketAddress() {
   return (InetSocketAddress) localSocket.getLocalSocketAddress();
 }