Заинтересовался сабжем. Дабы проверить - написал такой быдлокод, который делает (должен делать) следующее:
1) Сервер ждет соединений на порту 10222
2) Клиент подключается к серверу, узнает порт своего исходящего соединения и поднимает на этом порту свой сервер
3) Сервер пытается установить соединение с IP и портом создавшим это соединение (здесь IP и порт вашего провайдера, который, теоретически, должен нисмотря ни на что ссылаться на ваш внутренний IP и порт, это соединение открывший)
К сожалению, при запуске клиента за NAT получаю Connection Refused
Часть 1 - серверная сторона:
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleServer {
    ServerSocket ss;
    
    public void startServer() throws IOException{
        ss = new ServerSocket(10222);
        System.out.println("Сервер запущен по адресу: " + ss.getLocalSocketAddress());
    }
    
    public void stopServer() throws IOException{
        ss.close();
    }
    
    public void waitConnection() throws IOException, InterruptedException{
            Socket s = ss.accept();
            
            int remotePort = s.getPort();
            InetAddress host = s.getInetAddress();
            System.out.println("Пытаюсь установить соедиение с " + host.getHostAddress() + " на порт: " + remotePort);
            
            //попытка установления соединения с клиентом, который в этот момент ожидает соединения
            Thread.sleep(1000);
            Socket socket = new Socket(host, remotePort);
            System.out.println("Соединение установлено, отправляем тестовые данные");
            
            //Отправка данных
            OutputStream os = socket.getOutputStream();
            os.write("Hello, client!".getBytes());
            
            s.close();
    }
    
    public static void main(String[] args) throws InterruptedException, IOException{
        SimpleServer ss = new SimpleServer();
        ss.startServer();
        ss.waitConnection();
        ss.stopServer();
    }
}
Часть 2 - сторона клиента:
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleClient implements Runnable{
    SimpleClient(){}
    
    //
    SimpleClient(Socket socket){
        this.socket = socket;
        Thread t = new Thread(this);
        t.start();
    }
    Socket socket;
    
    String server = "192.168.10.10";
    int port;
    public void connect(){
        try {
            //Подключаемся к серверу
            Socket socket = new Socket(server, 10222);
            port = socket.getLocalPort();
            
            System.out.println("Обнаружен локальный порт: " + port);
            socket.setReuseAddress(true);
            
            //Создаем свой сервер на порту, использованному при исходящем соединении
            ServerSocket serverSocket = new ServerSocket(port);
            
            //Ждем соединений с созданного сервера
            while(true){
                System.out.println("Ожидание соединения на порту: " + port);
                Socket gotSocket = serverSocket.accept();
                System.out.println("Получено соединение");
                new SimpleClient(gotSocket); //Запустить поток, который будет читать сокет
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    //Чтение данных из сокета
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            int c;
            while( (c = is.read()) != -1 ){
                System.out.print((char) c);
            }
        } catch (IOException e) {
            System.err.println("Ошибка ввода вывода");
        }
    }
    
    public static void main(String[] args){
        new SimpleClient().connect();
    }
}


