how to change ip and port of client

i have multiplayer game witch i using udp hole punching approach on it.
the udp hole punching works well but when it comes to connecting the unity netcode i encounter an issue
in udp hole punching you have to bind the socket on specific port and ip
but in netcode you just can specify ip and the port of the server and client finds it own ip and port by himself

is there anyway in netcode that i can choose witch port and ip client must use?

clients have to use the IP address and port of the server

as much as i know when a packet gets send in a network there are 2 addresses in packet:

  • the first is the source address witch refers to the sender machine
  • second is the destination address witch refers to receiver machine

in unity netcode you can just specify a single port and ip
if you start host or server the address that you had defined will be set to the source address
if you start client it will sets as destination address and source address will be filled automatically

this images will explain everything:

9665273--1376420--udpholepunch.PNG

  • server is the top left window
  • client is the top right window
  • host is the bottom right window

the situation is that the host and client are behind different NATs over internet and they want to connect to each other directly
server’s task - witch it port is open - is to provide client and host’s public IP and port to each other
after that client and host can directly connect to each other

(as you can see the IPs are invalid and local. but the udp hole punching sector has tested in real situation before)

here is the code:

async void ConnectAsync(){
        int node = NodeType.value;
        disconnect = false;

        IPEndPoint recivedIp = null;
        IPEndPoint lep = new IPEndPoint(IPAddress.Parse(ClientIP.text),ushort.Parse(ClientPort.text));

        string Message = await Task.Run(() => {
            string message = "message is empty";
            if(node == 0){//Client
                udp.Send(ServerIP.text,us(ServerPort.text),"Client",lep);

                string response = udp.Listen(lep,out recivedIp);
                IPPort resp = Deserialize(response);
                //

                recivedIp.Address = IPAddress.Parse(resp.IP);
                recivedIp.Port = (ushort)resp.Port;
            
                string recMsg = "-1";
                string packet;
                int hand = 0;
                packet = hand.ToString();
                while(true){
                    try
                    {
                        udp.Send(recivedIp.Address.ToString(),(ushort)recivedIp.Port,packet,lep);
                        recMsg = udp.Listen(lep,out recivedIp,rnd.Next(0,5000));

                    }
                    catch{}
                    finally{
                    }
                    if(int.Parse(recMsg) == 0){
                        hand = 1;
                        packet = hand.ToString();
                    }
                    else if(int.Parse(recMsg) == 2){
                        break;
                    }
                }
                message = recMsg;
            }
            else if(node == 1){//Server
                    string response;
                    IPEndPoint recivedIp;
                    IPEndPoint lep = new IPEndPoint(IPAddress.Parse(ServerIP.text),ushort.Parse(ServerPort.text));

                    IPPort Client = new IPPort();
                    IPPort Host = new IPPort();

                    for (int i = 0; i < 2; i++)
                    {
                        response = udp.Listen(lep,out recivedIp);

                        IPPort resp = new IPPort{
                            IP = recivedIp.Address.ToString(),
                            Port = recivedIp.Port,
                            NodeType = response
                        };

                        if(response == "Client"){
                            Client = resp;
                        }
                        else{
                            Host = resp;
                        }
                    }
                    udp.Send(Client.IP,(ushort)Client.Port,Serialize(Host),lep);

                    udp.Send(Host.IP,(ushort)Host.Port,Serialize(Client),lep);
                    return "OperationEnded";
                }
            else if(node == 2){//Host

                udp.Send(ServerIP.text,us(ServerPort.text),"Host",lep);

                string response = udp.Listen(lep,out recivedIp);
                IPPort resp = Deserialize(response);
                //

                recivedIp.Address = IPAddress.Parse(resp.IP);
                recivedIp.Port = (ushort)resp.Port;

                string recMsg = "-1";
                string packet;
                int hand = 0;
                packet = hand.ToString();
                while(true){
                    try
                    {
                        udp.Send(recivedIp.Address.ToString(),(ushort)recivedIp.Port,packet,lep);
                        recMsg = udp.Listen(lep,out recivedIp,rnd.Next(0,5000));

                    }
                    catch{}
                    finally{
                    }
                    if(int.Parse(recMsg) == 1){
                        hand = 2;
                        packet = hand.ToString();
                        udp.Send(recivedIp.Address.ToString(),(ushort)recivedIp.Port,packet,lep);
                        break;
                    }
                }
                message = recMsg;
            }
            return message;
        });
        this.MessageText.text = Message;

        IPEndPoint hostIp = recivedIp;
        IPEndPoint clientIp = null;

        if(node == 0){//client
            unityTransport.ConnectionData.Address = lep.Address.ToString();
            unityTransport.ConnectionData.Port = (ushort)lep.Port;

            NetworkManager.Singleton.StartClient();
            this.MessageText.text = "client started";

            byte[] rec;

            await Task.Run(()=>{
                udp.Listen(lep,out recivedIp);
            });
            clientIp = recivedIp;

            Debug.Log("host: " + hostIp.Address + " | " + hostIp.Port);
            Debug.Log("client: " + clientIp.Address + " | " + clientIp.Port);

            while(!disconnect){
                rec = await Task.Run(()=>{
                    return udp.ListenBytes(lep,out recivedIp);
                });
                if(recivedIp.Address.ToString() == hostIp.Address.ToString()
                && recivedIp.Port == hostIp.Port){

                    udp.SendBytes(clientIp.Address.ToString(),(ushort)clientIp.Port,rec,lep);

                }
                else if(recivedIp.Address.ToString() == clientIp.Address.ToString()
                && recivedIp.Port == clientIp.Port){

                    udp.SendBytes(hostIp.Address.ToString(),(ushort)hostIp.Port,rec,lep);

                }
                else{
                    Debug.Log("Unknow IP and Port " + "| " + recivedIp.Address.ToString() + " | " + recivedIp.Port);
                }
            }
        }
        else if(node == 2){//host
            unityTransport.ConnectionData.Address = lep.Address.ToString();
            unityTransport.ConnectionData.Port = (ushort)lep.Port;

            NetworkManager.Singleton.StartHost();
            this.MessageText.text = "host started";
        }
        Debug.Log("ConnectAsync Done!");    
    }

the code is using udp socket to listen on the port and ip witch is given to client and shows the address in console log.
here is the result:

9665273--1376423--udpholepunch2.PNG

as you can see to port of the client is 51378 witch is never given to any machine.
(note that ip is still remain as 192.168.1.100 and that is because it is tested on machine’s local network. in actual test it will sets automatically. like the port was)

for udp hole punching i have to specify both client and host’s ip and port addresses
otherwise udp hole punch will fails
unity doesn’t let you to perform this.
there is only one connection data in network transport:

9665273--1376447--udpholepunch3.PNG
(the server listen address is completely ineffective. also you cant specify port for it)

Both Address fields should be 0.0.0.0 for the server/host, this means “listen to any address”. Not sure if “empty” has the same meaning, docs specifically mention the 0.0.0.0 address. The 127.0.0.1 address is set by default to prevent every project from opening public ports every time you enter playmode.

The Port is the incoming port on the server-side. This is what the clients need to specify, along with the server’s or client-host’s public IP address.
The 51378 port you’re seeing is likely just the client-side in/out port for the connection, which is irrelevant. The client should still be sending data to port 9999. Perhaps this port is chosen automatically if you run multiple processes on the same machine because if the server starts, it’ll block port 9999 for use by clients (or any other process). Likewise, if you run both Server and Host processes, they cannot use the same port or netcode will fail to initialize.

This is what a relay server provides. It will not be possible for a client-hosted machine to do this because the client-hosted machine itself is likely to be behind NAT.

You’ll need a relay anyway, because hole punching is not guaranteed to work and you end up with clients not being able to connect if the game does not fall back to relay.

i just test that with using different machine as client.
i have started host as port 8888 witch is free and no socket or app is binding it.
client’s 8888 port is also free. but it sends data with port number 37561 (basically every time a different port)

The port the client sends and receives data is irrelevant.
It’s likely randomized precisely because the client’s send/receive port doesn’t matter for any client-server application. At the same time it would be an easy attack vector if clients using a specific service would also receive data on that same port.