Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Questions to do with NetworkTransport

Discussion in 'Multiplayer' started by TheUKDude, Feb 23, 2017.

  1. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Ok, I am creating separate Client and Server (Linux Headless Mode) applications using the LLAPI NetworkTransport on which I have a very basic working version that connects, disconnects and sends information using the DataEvent.

    So there is no issue there as its working as I would of expected it.
    I can see some class functions that require arguments to do with relaying, so where I am not using relaying, can I just ignore that returned information.

    Like for example the function GetConnectionInfo(...) both returned arguments network and dstNode both return Invalid, is that still fine, error returns 0 so no errors.

    I know its handling all the actual connection and disconnection, message counter id's plus other checks behind the scenes, but I was also wondering if it does other stuff behind the scenes, just so that I don't repeat stuff.

    So a list of stuff that it handles behind the scenes would be great :)

    Am I correct in thinking all I have to do once the ConnectEvent has been called is create my data structure using a form of byte buffer and just send and receive that and that it will be detected as a DataEvent in the code I have in the update function.

    I am also looking at the channels that I setup at the start, I can see that it is of type byte that gets returned so allowing for up to 256 (0 to 255) channels, what are these actually for, I can see this is used when sending and receiving network messages, soooo.

    Could I use those channels for like sending different types of information, I am aware it still using the same network connection between the client and server and that it is just a byte sent behind the scenes.

    So if I have say a set of data packets that need to be encrypted like authentication etc I just setup a channel with the required QosType and every time it receives a data event using that channel I know what handler to send that data to so it can decrypt it and then process that data.

    And its the same for voice and text chat, they could have their own channel as well and so on.

    Even though its only a byte being set in the UDP Wrapped Packet that gets sent, is there an overhead or cost having loads of channels.

    Is there anything else I should know to do with the NetworkTransport.

    Thanks

    Paul
     
  2. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Unet in general is currently lacking in documentation, so naturally you'll struggle to find answers to some of these questions without trial and error. To answer what I can though:

    It depends on what it is that you are doing, but that is essentially the idea, yeah. Ultimately, you can neither send or receive data if you are not connected so yeah you'll want to wait until the connect event (where the connectionId is the one given to you via the NetworkTransform.Connect method, to confirm that it is you) before sending/receiving data is possible.

    One thing worth noting here is that a call to NetworkTransport.Receive or NetworkTransport.ReceiveFromHost will only give you one message, even if there are a number of messages queued. I tend to call Receive until you get a None result, to ensure that you get all queued messages each frame.

    A "channel" in unet has two purposes:
    • a channel defines the "quality of service", such as "reliable" to guarantee that every message sent on the channel is delivered, or "unreliable" to allow for natural package loss
    • channels can be used to separate messages between clients. For example you could use channels as a way to implement interest management, and have players "subscribe" to a channel to receive messages that are sent on that channel
    Notice that in NetworkTransport.Send you specify the channel id (to identify which channel the data is sent on). As you've discovered already, the channel id is given to you by ConnectionConfig.AddChannel

    Yeah as mentioned above, you can use channels pretty effectively to separate the messages being sent. I assume that is what you mean by "different types of information"?

    In NetworkTransport.Receive or NetworkTransport.ReveiveFromHost, you are given the channel id that the data came in on, so you could in theory use that as a way to handle the incoming data in a particular way, if that is how you plan to use channels.

    Note however that if you are using UDP (which you will be by default) to transfer data between peers, UDP is not a secure protocol. It suffers the same issue has http, in that it is possible to intercept a message transmitted over UDP and view the contents. Sending "encrypted" messages will be okay though, provided the "key" to decrypt the data does not exist in any code that you release to the public. For example, if you are sending encrypted messages between two servers then you will be fine. But if you're sending encrypted messages to a client, then that client will have the key in their code in order to be able to decrypt the message. Code can always be de-compiled so it's good to be aware of this.

    Yeah, this would be a good way to make use of channels. However, note that every channel makes use of the same resources. So, if one channel is taking time to send messages, it will slow other channels down too.

    It's fairly common that chat and voice servers are set up as separated servers from the main game servers, for this very reason, since sending voice and chat messages can quickly result in large amounts of information being sent over the network.

    There's more than 1 byte of overhead in messages sent via NetworkTransport.Send. I haven't been able to determine exactly how many, but there is certainly one byte used to specify the channel and a few more (likely used internally via unet).

    Having 1 channel versus having 255 channels though won't change performance. The performance is usually based on how much traffic is being sent over the network as a whole. For example, sending 10 messages per second on one channel versus sending 1 message over 10 channels per second will yield virtually identical results.

    There certainly is! However, as mentioned initially documentation is some-what lacking at the moment, and you are almost certainly going to be better off playing around with it and doing trial-and-error. Feel free to ask though if you have more questions.
     
    Last edited: Feb 24, 2017
    F likes this.
  3. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Thanks for the reply.

    I have been doing coding for networking applications for many years now, in C++ (long time ago) and C# for several years, just not in unity, so I was socked when I tried to port over my C# code into unity and it didn't work, but then it would of also been a shock if it did work LOL.

    Sorry for the way I replied below, post would of been way too long.

    So here goes...

    To-do with NetworkTransport.Receive

    Yeah, I was referring to once I had connected.
    I just wasn't too sure what else unity did behind the scenes.

    As to calling Receive(...) in a loop until it received a NetworkEventType.Nothing, I was thinking about that funny enough, but didn't want to do a bulk lot of processing in one update loop.
    I was also thinking about using Threads, but knowing my luck some functions will moan about it needing to be called in the main thread :p
    So something to try later I guess.

    To-do with channels...
    Yeah, I have set the QosType required for each of the channels that they need, I could of set all of them to ReliableFragmented, but the overhead would of been pretty large due to all UDP packets would / might need to be held back to make sure they are all in order and in sequence, so I only set my authentication channel as that the rest are set as reliable.

    To-do with Authentication over UDP
    Well I am using verified RSA Keys for this, the server sends the client the public key when the client first connects, which the client uses to send stuff to the server, the server then uses its Private Key to decrypt the data sent.

    Now there are two ways to do this.
    1: Use a verified RSA Server Side and give the clients the public key each time when they first connect.
    Only downside would be that the man in the middle would be able to use the server public key to see what the server sends back to any client.

    2: Same as way 1, but also have each client create its own (unverified) RSA Key to which the client sends the server its own public key to the server and the server uses that to talk to the client.
    So now we have secure connection both ways.

    The only issue is would all supported platforms in unity have access to the required namespaces needed to do this.
    I guess I could only but try.

    To-do with Channels and shared resources

    Yeah, I was aware of this, but that would also be the case even if I wasn't using channels, like I said above, I would really like to use Threads for this, but wasn't too sure if the code used would moan about not being called in the main thread.

    As for splitting up services, yeah that was one thing I was thinking about, I know I was thinking about doing just that for certain areas in the game to create a form of zoning where a player going from one zone to another would cause the player to hop servers, but that was something I would add later once the game picks up.

    To-do with Channels and overhead
    I wasn't referring to the 1 byte being the overhead of everything, but the space in the actual UDP packet that NetworkTransport sends and receives, that is one byte.

    I did try at first to write my own server side network code that would work with NetworkTransport, I spent a few days snooping on the packets being sent to each other and I did get about 80% understanding of what the data being sent was (id's counters, timestamps, channels, checksums etc), sadly this would of been less efficient than using Unity as the server so I stopped.

    As for the difference between using 1 channel or 255 channels, yeah I just wasn't sure what unity was doing in the background.
    But it seems its ok.

    To-do with the unknowns of NetworkTransport due to the lack of documentation, yeah, I have always gone the trial by error route, I always seem to learn more that way, and you also get to figure out why doing it a way didn't work, loads of fun.
     
  4. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Does anyone know what each of the byte error codes mean?

    Thanks

    Paul
     
  5. Elfinnik159

    Elfinnik159

    Joined:
    Feb 24, 2013
    Posts:
    145
    https://docs.unity3d.com/ScriptReference/Networking.NetworkError.html
    0 - Ok The operation completed successfully.
    1 - WrongHost The specified host not available.
    2 - WrongConnection The specified connectionId doesn't exist.
    3 - WrongChannel The specified channel doesn't exist.
    4 - NoResources Not enough resources are available to process this request.
    5 - BadMessage Not a data message.
    6 - Timeout Connection timed out.
    7 - MessageToLong The message is too long to fit the buffer.
    8 - WrongOperation Operation is not supported.
    9 - VersionMismatch The protocol versions are not compatible. Check your library versions.
    10 - CRCMismatch The ConnectionConfig does not match the other endpoint.
    11 - DNSFailure The address supplied to connect to was invalid or could not be resolved.



    It is important where it pops up an error - the server or client.

    Alas, I do not know English very well and I can not perfectly understand the large text. I understand that there is a problem with congestion and because of her error code 6 (time out)

    I am a long time looking for a solution to this problem. As it turns out, you need to send packets to not more than 10 times per second, the same object. In one package!

    I wrote code that stores all the data on the client side, and every tenth of a second and combines them server sends a single packet over a channel with guaranteed delivery. The same is true on the server side for each client. It completely corrected the problem with congestion, despite the fact that I am posting this on the channel with guaranteed delivery.

    the maximum packet size - 65536 bytes
     
  6. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Thanks for the list, I was getting error code 2 on the server side when I was getting the connection information.
    Which makes sense now due to I was getting the info when a new connection happens and when they disconnect.
    So of course it would fail that connection has left LOL.

    I think you are referring to latency.
     
  7. Elfinnik159

    Elfinnik159

    Joined:
    Feb 24, 2013
    Posts:
    145
    You can display the code of the host establishment and the connection?
     
  8. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Yeah, on the ConnectEvent and DisconnectEvent in Update() I have this code:
    Code (CSharp):
    1. string sAddress;
    2. int iPort;
    3. ulong ulNetwork;
    4. ushort usNode;
    5. UnityEngine.Networking.Types.NetworkID networkID;
    6. UnityEngine.Networking.Types.NodeID nodeID;
    7.  
    8. NetworkTransport.GetConnectionInfo(outHostId, outConnectionId, out sAddress, out iPort, out networkID, out nodeID, out error);
    9.  
    10. Debug.LogFormat("ConnectEvent - GetConnectionInfo({0}, {1}, {2}, {3}, {4}, {5}, {6})", outHostId, outConnectionId, sAddress, iPort, networkID, nodeID, error);
    This works rather well :)

    Paul
     
  9. Elfinnik159

    Elfinnik159

    Joined:
    Feb 24, 2013
    Posts:
    145
    at line 8 error==2?

    outConnectionId ==?
    Apparently, there is no connection with connectionID.


    You can get connectionID for debugging when you connect m_ConnectionId = NetworkTransport.Connect (m_GenericHostId, ip, port, 0, out error);

    Or you get it here?
    NetworkEventType recData = NetworkTransport.Receive(out recHostId, out outConnectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);

    I'm a little confused.
    Do I understand correctly, where the error?
     
  10. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    On line 8 the error argument returns 2 when I called that line on the server when a connection was disconnected.
    So that would be "The specified connectionId doesn't exist." due to they just left.

    Paul
     
  11. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    I'm not 100% sure, but I notice a fairly big performance difference between webgl builds and all other build types, when it comes to the networking. Webgl obviously builds to javascript, which doesn't support multithreading, so I have come to the conclusion that unet already handles the sending and receiving of data on a seperated thread. I can't be 100% sure of this though.

    If you were to implement your own threading of messages, it would be pretty simple to avoid the issues with certain things not being callable off of the main thread:
    • lock and queue up outgoing messages on in a list/dictionary on the main thread. Lock and consume/send on the worker thread
    • lock and queue up incoming messages on the worker thread. Consume and handle these incoming messages on the main thread
    I would be interested to see whether this does make a difference to performance, as this would help to confirm whether unet does infact send/receive on a separate thread. So if you go ahead with this, I would be interested to know the result.

    That's a really good way to do it. Looks like you're on the right track here!

    This unfortunately isn't secure, rendering the rest of your solution related to the use of your RSA keys useless. The issue here is that the RSA key is sent over an unsecure connection, which means that the initial transmission that you make to send the RSA key could be intercepted, and a hacker could easily make use of the RSA key that you have sent.

    Ultimately, there is simply no security involved in UDP. It is possible to write your own secure layer on top of UDP (some frameworks have done this, adopting similar methods to https using SSL certificates). So I would advise a re-think of how you transmit your RSA key. An alternative option could be to set up a set server over https, and have the web server generate and send your RSA key to the client over https. There are still a number of issues with this (such as how you protect the RSA key, and make it so that the game server can authenticate the key without sending it over UDP) however, this would be a better direction to take.

    I implemented something similar for a game I worked on recently. What I do:
    - the client has a session key (just a random string generated by the web server) that it stores in local memory to be re-used each time they start the game
    - they make a request to the web server to authenticate the session string over https. The web server response with a one-time-use token
    - the client sends this one-time-use token to the game server (over UDP). The game server also has a session string, generated by "logging in" to the web server over https. The game server sends both it's own session string, and game token, so that the web server can ensure that the game server is making the request. If all is well, the web server responds to the game server with the user's player data

    That sounds ideal. I also use channels to handle interest management, or "zoning" as you have called it. I find it to perform really well. It also simplifies things quite a bit because there is no "subscribing" or "unsubscribing" from objects this way. You simply add/remove channels based on the areas that you are interested in.

    I did the same roughly just after the LLAPI was released, and ended up with a fairly solid solution. Later updates of the LLAPI however changed the format of the internal byte arrays that unet was sending, resulting in me having to re-analyze the byte arrays and try and work out what the new structure was. I ended up giving up and just relying on the NetworkTransport.Receive methods, not worrying about the bytes that unet sends internally. So I would recommend the same, as it's likely that they might tweak the format of their byte arrays further as they improve unet.
     
  12. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Cast your error code to the NetworkError byte enum for more clarity:

    Code (CSharp):
    1. //the below is true
    2. (NetworkError)2 == NetworkError.WrongConnection
    NetworkTransport.Connect returns to you an integer, which is a way for you to identify a single peer over the socket. So for example, consecutive calls to NetworkTransport.Connect will continue to return a new number (incremented from the last value). Note however this value is only unique to your local socket. It cannot, for example, be used to identify a peer over the whole network as your connection id will differ between the client side and server side.

    The "outConnectionId" in your code in the NetworkTransport.Receive call tells you which connection (on your local socket) you are receiving data for.

    In a normal client-server architecture, your client side will only have one connection, in which case you don't need to worry about the connectionId too much. On the server-side though, you will be handling many connections. So the connectionId becomes important here as it tells you which of the connected peers you are receiving messages for.
     
    IgorAherne likes this.
  13. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Thanks for your reply.

    I am glad I am thinking on the right tracks.

    Well there is nothing the man in the middle can do with that public key, he cannot modify it or give me a completely new public key due to my cert is a RSA (SSL) Cert signed by a CA.

    Also the client creates its own RSA Key set and sends me its public key.
    Steps:
    1: Client requests for server public key.
    2: Server sends client its public key.
    3: Client uses the servers public key to send the clients public key to the server.

    Now when the server sends stuff encrypted to the client it uses the clients public key.
    And when the client sends stuff encrypted to the server it uses the servers public key.

    You always encrypt using a public key due to it can only be decrypted by its private key, you never encrypt anything with the private key that would just be silly :p

    So the man in the middle cannot do anything.
     
  14. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Agreed.
     
  15. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    I may well be misunderstanding your solution, however it's not whether the man-in-the-middle can modify the key that you would be concerned about.

    This is the part that opens up your solution to potential threat. Because you are currently sending the key to the client via UDP. A man-in-the-middle only has to read the content of that transmission. Upon doing so, they have the key that you intended to send to the client, and can therefore send it to the server pretending to be them.

    Again, I may be misunderstanding what it is you are intending to do here, but since we're on the topic of security I figured it would be best to make sure.
     
  16. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    I understand what you are saying, but HTTPS does exactly the same, when you initiate a HTTPS connection the server send the connecting client its SSL Cert (i.e. its Public Key) its not encrypted its in plaintext so at that point any information sent from the server is unencrypted.
    So any man in the middle could read it, however any information sent to the server would require the private key which the server only has, so all is fine.
    Its hard to see what the sends back to the server due to its encrypted using the servers public key,

    Also if your saying the man in the middle could then pretend to be the client, the same would happen for HTTPS.

    If you get me.

    Also there is no issues in giving out the public key to anything, its public that's what it is designed for.
    Loads of services give out their public keys.

    Unless I am missing something here.

    The actual classes that I have used in the past and will be using is System.Net.Security.SslStream where I can have the server AuthenticateAsServer for the server side and AuthenticateAsClient for the client side and it does exactly the same thing as HTTPS.

    I will give this a go and see how it works and if any issues arises I will have to rethink this.

    Thanks
    Paul
     
    Last edited: Feb 27, 2017
  17. donnysobonny

    donnysobonny

    Joined:
    Jan 24, 2013
    Posts:
    220
    Okay so this is definitely where the misunderstanding is. You are right in that this is how HTTPS works, however, HTTPS requires a secure layer protocol (such as TLS, although any secure layer protocol could be used here) in order to function correctly. The SSL part is also required, and is used to verify that the server end is who they say they are.

    The important part to understand here is that HTTPS ultimately works because of the combination of TCP, TLS (or another secure layer protocol) and SSL. TCP is the transfer protocol being used (similar to UDP, but TCP is a reliable transfer protocol while UDP isn't). Both the HTTP and SSL part require TCP, in order to function correctly.

    So in short, if you are using UDP as your transfer protocol (which you are by default, when using unet), you will not be able to make use of the same protocols that HTTPS use to guarantee security. The AuthenticateAsServer and AuthenticateAsClient merely expect you to specify the client/server end details to validate the keys/certificates. Although it doesn't specify anywhere in the documentation, you would be expected to use these in conjunction with HTTPS in order to actually provide secure transfer of the data being sent between the client and server. So if you are for example using these on the client/server end of a UDP connection, then the solution is not technically secure.

    This thread is potentially getting a little too off-topic from your original post now, so if you need further advice on this I'd maybe suggest opening up a new thread, which may in turn encourage additional opinions on the matter. Alternatively, I would recommend the solution I suggested in a previous reply (send your sensitive data over HTTPS via a web server, and then handle everything else over UDP with your game server), or have a look into different ways of adding a secure layer on top of UDP (such as wrapping the UDP connection in TCP).

    Hopefully this helps!
     
  18. TheUKDude

    TheUKDude

    Joined:
    Jul 27, 2013
    Posts:
    72
    Thanks for the reply.
    This is my final reply on this matter due to you are right about it going off topic.

    Yeah, sadly I just couldn't get to work with NetworkTransport due to no stream to hook into.

    I did end up getting RSA and AES KeyExchange working so I might see how that goes and if that don't go as planned I still have HTTPS.

    I might open a post to do with this later on.

    Paul
     
    donnysobonny likes this.