C# Network Programming by Richard Blum Sybex © 2003 (647 pages) ISBN:0782141765 This book helps newcomers get started with a look at the basics of network programming as they relate to C#, including the language’s network classes, the Winsock interface, and DNS resolution BackCover C# Network Programming - Information Introduction How This Book Is Organized Part II: Network Layer Programming Part III: Application Layer Programming Examples Keeping Up to Date Part I: Network Programming Basics Chapter 1: The C# Language Basics of NET Installing a C# Development Environment The C# Runtime Environment C# Programming Basics C# Features Summary Chapter 2: IP Programming Basics Watching Network Traffic Analyzing Network Packets Programming with TCP and UDP Finding IP Address Information Summary Chapter 3: C# Network Programming Classes A Primer on Socket Programming C# Socket Programming C# Socket Helper Classes Summary Chapter 4: DNS and C# The Domain Name System (DNS) Windows DNS Client Information DNS Classes in C# Summary Part II: Network Layer Programing Chapter 5: Connection-Oriented Sockets A Simple TCP Client When TCP Goes Bad Using C# Streams with TCP Summary Chapter 6: Connectionless Sockets A Simple UDP Application Distinguishing UDP Messages When UDP Goes Bad A Complete UDP Application Summary Chapter 7: Using The C# Sockets Helper Classes The TcpClient Class The TcpListener Class The UdpClient Class Moving Data across the Network Summary Chapter 8: Asynchronous Sockets Windows Event Programming Using Asynchronous Sockets Sample Programs Using Asynchronous Sockets Using Non-blocking Socket Methods Summary Chapter 9: Using Threads Creating Threads in a Program Using Threads in a Server Using Threads for Sending and Receiving Data Thread Pools Using Thread Pools in a Server Summary Chapter 10: IP Multicasting What Is Broadcasting? Using Broadcast Packets to Advertise a Server What Is Multicasting? C# IP Multicast Support Sample Multicast Application Summary Part III: Application Layer Programming Examples Chapter 11: ICMP Using Raw Sockets Creating an ICMP Class A Simple Ping Program An Advanced Ping Program The TraceRoute.cs Program The FindMask Program Summary Chapter 12: SNMP Understanding SNMP Working with SNMP Packets Creating a Simple SNMP Class The SimpleSNMP Program Using Vendor MIBs Using GetNextRequest Queries Summary Chapter 13: SMTP E-mail Basics SMTP and Windows The SmtpMail Class Using Expanded Mail Message Formats Mail Attachments The MailAttachment Class A POP3 Client Summary Chapter 14: HTTP Advanced Web Classes Web Services Summary Chapter 15: Active Directory Working with Active Directory Using C# to Access a Network Directory Modifying Directory Data Searching the Network Directory Summary Chapter 16: Remoting Moving Data, Revisited An Overview of Remoting Using Remoting Creating a Proxy Class Using soapsuds Summary Chapter 17: Security Socket Permissions Protecting Network Data Protecting Network DataCryptoTest Enter phrase to encrypt: A test phrase Encrypted: ~/@'F?p{=.$g=R2U Decrypted: A test phrase C:\> Network Data Encryption Although it is convenient to use the CryptoStream class with FileStream and MemoryStream objects, it is often difficult to use it with NetworkStream objects The downside to using the CryptoStream class with NetworkStream objects is the same old issue of determining data boundaries within the data stream When data is encrypted and passed directly to a NetworkStream object, there is no easy way of determining when the receiver has received the end of the encrypted data If the decryptor attempts to decrypt the data before the end of the stream has arrived, an error will occur Handling Encrypted Data The solution to the data boundary issue is to deploy one of the common stream data handling techniques discussed in Chapter 7, 'Using the C# Sockets Helper Classes.' As described in Chapter 7, to handle stream data you can either use fixed-length messages, send the message size in the stream before the message, or use message boundary markers The easiest technique to use with encrypted messages is to determine the size of each encrypted message and send it in the stream before the actual message The receiving program can extract the message size from the NetworkStream and will know exactly how many bytes of data to read from the network to properly form the encrypted message The receiving program can then forward the entire encrypted message to the appropriate decryptor to extract the original data To determine the size of each encrypted message, the message must be stored in a buffer after being encrypted and before being sent out on the NetworkStream Once the encrypted message is in the buffer, it's easy to determine the size of the message and send both the message size and the message out on the NetworkStream A simple solution is to use the MemoryStream class, as demonstrated in the CryptoTest.cs program By forwarding the CryptoStream output to a MemoryStream, you can use the GetBuffer() method to create a byte array with the encrypted message The array can then be sent out on the network just like any other byte array of data On the receiver side, the opposite steps are performed Data read from the NetworkStream is fed for decryption into the CryptoStream object, which points to a MemoryStream object Once the entire decrypted message is in the MemoryStream object, it can be extracted using the GetBuffer() method Figure 17.2 illustrates both the sender and receiver processes in network data encryption Figure 17.2: Encrypting data for the network The CryptoDataSender Program The CryptoDataSender.cs program, Listing 17.4, demonstrates encryption of complex data for sending on the network CryptoDataSender takes the original BetterDataSender.cs program (Listing 16.6) and adds triple DES encryption to secure the data being sent Note Just like the original program, the CryptoDataSender program must be compiled with the SerialEmployee.dll file created in Chapter 16 using the /r: compiler option: csc /r:SerialEmployee.dll CryptoDataSender.cs Listing 17.4: The CryptoDataSender.cs program using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; using System.Security; using System.Security.Cryptography; using System.Text; class CryptoDataSender { private void SendData (NetworkStream strm, SerialEmployee em { IFormatter formatter = new SoapFormatter(); MemoryStream memstrm = new MemoryStream(); byte[] Key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0 byte[] IV = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 TripleDESCryptoServiceProvider tdes = new TripleDESCryptoSe CryptoStream csw = new CryptoStream(memstrm, tdes.CreateEnc formatter.Serialize(csw, emp); csw.FlushFinalBlock(); byte[] data = memstrm.GetBuffer(); int memsize = (int)memstrm.Length; byte[] size = BitConverter.GetBytes(memsize); strm.Write(size, 0, 4); strm.Write(data, 0, (int)memsize); strm.Flush(); csw.Close(); memstrm.Close(); } public CryptoDataSender() { SerialEmployee emp1 = new SerialEmployee(); SerialEmployee emp2 = new SerialEmployee(); emp1.EmployeeID = 1; emp1.LastName = "Blum"; emp1.FirstName = "Katie Jane"; emp1.YearsService = 12; emp1.Salary = 35000.50; emp2.EmployeeID = 2; emp2.LastName = "Blum"; emp2.FirstName = "Jessica"; emp2.YearsService = 9; emp2.Salary = 23700.30; TcpClient client = new TcpClient("127.0.0.1", 9050); NetworkStream strm = client.GetStream(); SendData(strm, emp1); SendData(strm, emp2); strm.Close(); client.Close(); } public static void Main() { CryptoDataSender cds = new CryptoDataSender(); } } If you remember, the BetterDataSender program used SOAP serialization to serialize an employee data object for sending to a remote device across the network Because the SOAPFormatter was used, all of the data was sent in text mode across the network Should that data include sensitive information such as a Social Security number, anyone viewing the packet would see the information You don't want this to happen, of course, in your applications The CryptoDataSender program creates the same SOAP serialization of the data, but then it runs it through the CryptoStream object to encrypt the information before sending it out: formatter.Serialize(csw, emp); csw.FlushFinalBlock(); byte[] data = memstrm.GetBuffer(); int memsize = (int)memstrm.Length; byte[] size = BitConverter.GetBytes(memsize); strm.Write(size, 0, 4); strm.Write(data, 0, (int)memsize); The SOAPFormatter Serialize() method serializes the employee data object to a stream but uses the CryptoStream stream object instead of the NetworkStream The FlushFinalBlock() method ensures that all of the encrypted blocks are flushed out of the buffer Because the CryptoStream object pointed to a MemoryStream object, that object contains the entire encrypted, SOAP-serialized, SerialEmployee data object The MemoryStream object is then converted to a byte array, the size of the data is determined, and both are sent out on the NetworkStream object to the remote receiver The CryptoDataRcvr Program Now let's look at the receiving program The CryptoDataRcvr.cs program, Listing 17.5, demonstrates how to read and decrypt data from a NetworkStream As expected, the CryptoDataRcvr.cs program mimics the BetterDataRcvr program of Listing 16.7 Note Again, the CryptoDataRcvr program must be compiled with the SerialEmployee.dll file from Chapter 16 using the /r: option: csc /r:SerialEmployee.dll CryptoDataRcvr.cs Listing 17.5: The CryptoDataRcvr.cs program using System; using System.IO; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; using System.Security; using System.Security.Cryptography; using System.Text; class CryptoDataRcvr { private SerialEmployee RecvData (NetworkStream strm) { MemoryStream memstrm = new MemoryStream(); byte[] Key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x0 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}; byte[] IV = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}; TripleDESCryptoServiceProvider tdes = new TripleDESCryptoSe CryptoStream csw = new CryptoStream(memstrm, tdes.CreateDec CryptoStreamMode.Write); byte[] data = new byte[2048]; int recv = strm.Read(data, 0, 4); int size = BitConverter.ToInt32(data, 0); int offset = 0; while(size > 0) { recv = strm.Read(data, 0, size); csw.Write(data, offset, recv); offset += recv; size -= recv; } csw.FlushFinalBlock(); IFormatter formatter = new SoapFormatter(); memstrm.Position = 0; SerialEmployee emp = (SerialEmployee)formatter.Deserialize( memstrm.Close(); return emp; } public CryptoDataRcvr() { TcpListener server = new TcpListener(9050); server.Start(); Console.WriteLine("Waiting for a client "); TcpClient client = server.AcceptTcpClient(); NetworkStream strm = client.GetStream(); SerialEmployee emp1 = RecvData(strm); Console.WriteLine("emp1.EmployeeID = {0}", emp1.EmployeeID) Console.WriteLine("emp1.LastName = {0}", emp1.LastName); Console.WriteLine("emp1.FirstName = {0}", emp1.FirstName); Console.WriteLine("emp1.YearsService = {0}", emp1.YearsServ Console.WriteLine("emp1.Salary = {0}\n", emp1.Salary); SerialEmployee emp2 = RecvData(strm); Console.WriteLine("emp2.EmployeeID = {0}", emp2.EmployeeID) Console.WriteLine("emp2.LastName = {0}", emp2.LastName); Console.WriteLine("emp2.FirstName = {0}", emp2.FirstName); Console.WriteLine("emp2.YearsService = {0}", emp2.YearsServ Console.WriteLine("emp2.Salary = {0}", emp2.Salary); strm.Close(); server.Stop(); } public static void Main() { CryptoDataRcvr cdr = new CryptoDataRcvr(); } } In Chapter 16's BetterDataRcvr program, the data was read from the NetworkStream and fed directly into the SOAP Deserialize() method Here in the CryptoDataRcvr program, however, there is obviously another step involved Because the new data is encrypted, it must be decrypted before it can be deserialized First, the size of the encrypted data array is read from the NetworkStream Knowing the size of the data array to expect, a while loop ensures that all of the encrypted data is read from the network: while(size > 0) { data = new byte[2048]; recv = strm.Read(data, 0, size); csw.Write(data, offset, recv); offset += recv; size -= recv; } csw.FlushFinalBlock(); As each block of data is read from the network, it is fed to the CryptoStream stream, which decrypts the data and stores it in the created MemoryStream object When all of the data has been received from the network, the FlushFinalBlock() method ensures that all of the decrypted data blocks have been sent to the MemoryStream After all of the decrypted data is placed in the MemoryStream object, the pointer is reset, and the MemoryStream object is used in the Deserializer() method to produce the original employee data object Testing the Programs After compiling both the CryptoDataSender.cs and CryptoDataRcvr.cs programs (using the SerialEmployee.dll file), you can test them out on your network The final result should be the complete data for both employee records: C:\>CryptoDataRcvr Waiting for a client emp1.EmployeeID = 1 emp1.LastName = Blum emp1.FirstName = Katie Jane emp1.YearsService = 12 emp1.Salary = 35000.5 emp2.EmployeeID = 2 emp2.LastName = Blum emp2.FirstName = Jessica emp2.YearsService = 9 emp2.Salary = 23700.3 C:\> As shown in the output, both employee data objects sent by the sender were successfully received by the receiver However, this does not prove that the encryption really did take place The only way to see this is to watch the packets as they are sent across the network If you are testing these applications on separate devices on a network, you can use the WinDump or Analyzer programs to watch the packets on the network Figure 17.3 shows one packet in the Analyzer program You can see that the data contained in the packets has indeed been encrypted, preventing the casual network snooper from seeing your data Figure 17.3: Displaying a data packet in the Analyzer program Summary With today's constant worries about security, you need to know how to incorporate security features in your C# network programs This chapter presented various NET security features that can be easily used in programs The NET CLR uses security policies to determine how (and if) applications run on the system Each application is a member of a security group based on characteristics of the application (such as the location or the author of the application) The CLR system uses security permission sets to determine each security group's permissions for local system resources such as files and network sockets You can use the caspol program, included in NET, to determine the security settings for a machine and to modify security settings to grant your applications the appropriate permissions to access network sockets Often, if applications are run from a remote file server disk share, they will not have sufficient permissions to access network sockets on the system To solve this problem, you can add the appropriate group (usually intranet) to the Everything permission set The NET network library also includes classes for securing the network sockets that applications can access The SocketPermissionAttribute class allows you to define attribute commands within your C# network programs to block customers from using specified sockets or IP addresses Each metadata command defines a security attribute that is implemented on the application If a security attribute specifies to deny access to a specific network port, the application will throw a SecurityException if the port is accessed Sending data across the network to a remote network host is dangerous Depending on the network, there can be many prying eyes watching at data packets as they traverse the network To protect your data, it is a good idea to implement some type of encryption technique before sending it out on the network The NET library includes several excellent encryption classes to assist you in this task The easiest way to implement encryption in a network program is to use a symmetric encryption class Symmetric encryption allows the data to be encrypted in variable-length blocks, which can be chained together to form a stream The encrypted data stream can then be passed to any other type of stream, including a NetworkStream Often though, it is easier to handle the data by placing it in a MemoryStream and creating a byte array from the stream data The byte array can then be safely sent across the NetworkStream as normal, using standard network stream techniques When the data is received on the other end, it must be decrypted back to the original data using the same key pair that was used to encrypt it Over the past 17 chapters, we have covered lots of networking topics I hope you have enjoyed your experience in learning network programming and that you're ready to go off and create your own network applications With the advent of new networking technologies, there is always something new to learn in the area of network programming It is a good idea to stay in touch with the current network trends by browsing networking newsgroups and keeping up with the latest RFCs Happy networking ... interface to access network resources Chapter 3, C# Network Programming Classes,” offers a quick introduction to the entire C# network libraries and shows the basic formats of the classes Chapter 4, “DNS and C# ,” rounds out the introductory section by... Web Services Summary Chapter 15: Active Directory Working with Active Directory Using C# to Access a Network Directory Modifying Directory Data Searching the Network Directory Summary Chapter 16: Remoting... The next group of chapters presents the core of network programming topics in the book Each of these chapters discusses a major topic using in creating C# network programs Chapter 5, “Connection-Oriented Sockets,” starts the discussion of