수안이의 컴퓨터 연구실

  • Mainpage
  • About Me
  • Tags
  • Metapage
  • Notice
  • Location
  • Keywords
  • Guestbook
  • Admin
  • Write an Article
  • Total | 1620702
  • Today | 82
  • Yesterday | 482

17 Articles, Search for 'Programming/Network Programming'

  1. 2007/07/27 Socket Programming in C#
  2. 2007/07/27 Network Programming in C#
  3. 2007/07/26 IOCP Thread Pooling in C#
  4. 2007/05/14 UDP 프로그래밍의 기초
  5. 2007/05/14 ICMP 프로그래밍
  6. 2007/05/14 libpcap 프로그래밍
  7. 2007/05/14 pcap 을 이용한 id,password 정보가져오기
  8. 2007/05/11 SNMP개요및 설치,운용
  9. 2007/05/11 SNMP응용 프로그램 제작
  10. 2007/05/11 libpcap을 이용한 포트스캐닝 검사 (2)
«Prev  1 2  Next»
Programming/Network Programming2007/07/27 09:22

Socket Programming in C#

출처 : http://www.devarticles.com/c/a/c-sharp/ ··· art-i%2F

Socket Programming in C# - Part I
(Page 1 of 2 )

If you have dealt with sockets in the past, you may be interested in learning how it is done using C# technology. Read more ...The purpose of this article is to show you how you can do socket programming in C#. This article assumes some familiarity with the socket programming, though you need not to be expert in socket programming. There are several flavors to socket programming - like client side , server side , blocking or synchronous , non-blocking or asynchronous etc.

With all these flavors in mind , I have decided to break this subject into two parts. In the part 1 I  will start with the client side blocking socket. Later on in the second part I will show you how to create server side and non-blocking.


Socket Programming in C# - Part I - The Article
(Page 2 of 2 )

Network programming in windows is possible with sockets. A socket is like a handle to a file. Socket programming resembles the file IO as does the Serial Communication. You can use sockets programming to have two applications communicate with each other. The application are typically on the different computers but they can be on same computer. For the two applications to talk to each either on the same or different computers using sockets one application is generally a server that keeps listening to the incoming requests and the other application acts as a client and makes the connection to the server application.

The server application can either accept or reject the connection. If the server accepts the connection, a dialog can begin with between the client and the server.  Once the client is done with whatever it needs to do it can close the connection with the server. Connections are expensive in the sense that servers allow finite connections to occur.  During the time client has an active connection it can send the data to the server and/or receive the data.

The complexity begins here. When either side (client or server) sends data the other side is supposed to read the data. But how will the other side know when data has arrived. There are two options - either the application needs to poll for the data at regular intervals or there needs to be some sort of mechanism that would enable application to get notifications and application can read the data at that time. Well , after all Windows is an event driven system and the notification system seems an obvious and best choice and it in fact is.

As I said the two applications that need to communicate with each other   need to make a connection first. In order for the two application to make connections the two applications need to identify each other ( or each other's computer ). Computers on network have a unique identifier called  I.P. address which is represented in dot-notation like 10.20.120.127 etc. Lets see how all this works in .NET.

System.Net.Sockets Namespace

Before we go any further, download the source code attached with this article. Extract the zip file to a folder say c:\Temp you will see following two folders :

  • Server
  • Client

In the Server folder there will be one EXE . And in the client there will be the source code in C# that is our client. There will be one file called SocketClient.sln  which the solution file . If you double click that your VS.NET will be launched and you will see the  project SocketClientProj in the solution. Under this project you will have SocketClientForm.cs file.  Now build the code ( by pressing Ctrl-Shift-B) and run the code you will see the following dialog box:

As you can see the dialog box has a field for Host IP address ( which is the IP address of the machine on which you will run the Server Application ( located under Server folder) ). Also there is a field where you can specify port number at which the Server is listening. The server app I have provided here listens at port 8221. So I have specified port to be 8221.


After specifying these parameters we need to connect to the server. So pressing Connect will connect to the server and to close the connection press Close. To send some data to the server type some data in the field near the button name Tx and if you press Rx the application will block unless there is some data to read.
With this info lets now try to check the code behind this:

Socket programming in .NET is made possible by Socket class present inside the System.Net.Sockets namespace.
Socket class  has several method and properties and a constructor.
The first step is to create an object of this class.
Since there is only one constructor we have no choice but to use it.

Here is how to create the socket:

m_socListener = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.IP);

The first parameter is the address family which we will use interNetwork - other options include Banyan NetBios etc.
AddressFamily is an enum defined in Sockets namespace.
Next we need to specify socket type: and we would use reliable two way connection-based sockets (stream) instead of un-reliable Connectionless sockets ( datagrams) . So we obviously specify stream as the socket type and finally we are using TCP/IP so we would specify protocol type as Tcp.

Once we have created a Socket we need to make a connection to the server since we are using connection-based communication.
To connect to the remote computer  we need to know the IP Address and port at which to connect.
In .NET there is a class under System.Net namespace called IPEndPoint which represents a network computer  as an IP address and a port number.

The IPEndPoint has two  constructors - one that takes a IP Address and Port number  and one that takes long and port number. Since we have computer IP address we would use the former

public IPEndPoint(System.Net.IPAddress address, int port);

As you can see the first parameter takes a IPAddress object. If you examine the IPAddress class you will see that it has a static method called Parse that returns IPAddress given a string ( of dot notation ) and second parameter will be the port number. Once we have endpoint ready we can use Connect method of Socket class to connect to the end point  ( remote server computer ).
Here is the code:

System.Net.IPAddress ipAdd = System.Net.IPAddress.Parse("10.10.101.200");
System.Net.IPEndPoint remoteEP = new IPEndPoint (iAdd,8221);
m_socClient.Connect (remoteEP);

These three lines of code will make a connection to the remote host running on computer with IP 10.10.101.200 and listening at port 8221. If the Server is running and started ( listening ), the connection will succeed. If however the server is not running an exception called SocketException will be thrown. If you catch the   exception and check the Message property of the exception in this case you see following text:

"No connection could be made because the target machine actively refused it."

Similarly if you already have made a connection and the server somehow dies , you will get following exception if you try to send data.

"An existing connection was forcibly closed by the remote host"

Assuming that the connection is made, you can send data to other side using the Send method of the Socket class.
Send method has several overloads. All of them take a byte array . For example if you want to send "Hello There" to host you can use following call:

try
{
String szData = "Hello There";
byte[] byData = System.Text.Encoding.ASCII.GetBytes(szData);
m_socClient.Send(byData);
}
catch (SocketException se)
{
MessageBox.Show ( se.Message );
}

Note that the Send method is blocking. What it means the call will block till the data has been sent or an exception has been thrown. There is an non-blocking version of the send which we will discuss in the next part of this article.
Similar to Send there is a Receive method on the Socket class. You can receive data using following call:

byte [] buffer = new byte[1024];
int iRx = m_socClient.Receive (buffer);

The Receive method again is blocking. It means that if there is no data available the call will block until some data arrives or an exception   is thrown.

Non-blocking version of Receive method is more useful than the non-blocking version of Send because if we opt for block Receive , we are effectively doing polling. There is no events about data arrival. This model does not work well for serious applications. But all that is the subject of our next part of this article. For now we will settle with the blocking version.
In order to use the source code and application here you would need to run the Server first:

Here is the way Server looks like:

When you launch the Server, click Start to start listening. The Server listens at port 8221. So make sure you specify the port number 8221 in the port field of our client application. And in the IPAddress field of Client App enter the IP Address of the machine on which the Server is running. If you send some data to server from the client by pressing Tx button, you will see that data in the grayed out edit box.


Socket Programming in C# - Part II
(Page 1 of 2 )

This is the second part of Ashish's two part series about handling sockets in the C# language. Read this article to learn how to use sockets with the .Net framework.This is the second part of the previous article about the socket programming. In the earlier article we created a client but that client used to make blocking IO calls ( Receive ) to read data at regular intervals (via clicking the Rx button). But as I said in my earlier article, that model does not work very well in a real world application. Also since Windows is an events-based system, the application (client) should get notifications of some kind whenever the data is received so that client can read it rather than client continuously polling for data.

Well that is possible with a little effort. If you read the first part of this article, you already know that the  Socket class in  the Systems.Net.Sockets namespace has several methods like  Receive and Send which are blocking calls. Besides there are also functions like BeginReceive , BeginSend etc. These are meant for asynchronous IO . For example , there are at least two problems with the blocking Receive:

  1. When you call Receive function the call blocks if no data is present, the call blocks till some data arrives.
  2. Even if there is data when you made the receive call , you don't know when to call next time. You need to do  polling  which is not an efficient way.
Socket Programming in C# - Part II - Article
(Page 2 of 2 )

 

Although you can argue that one can overcome these shortcomings by multithreading meaning that one can spawn a new thread and let that thread do the polling and notifies the main thread of the data. Well this concept will work well. But even if you create a new thread it would require your main thread to share the CPU time with this new thread. Windows operating system (Windows NT /2000 /XP) provide what is called Completion Port IO model for doing overlapped ( asynchronous) IO.

The details of IO Completion port are beyond the scope of the current discussion, but to make it simple you can think of IO Completion Ports as the most efficient mechanism for doing asynchronous IO in Windows that is provided by the Operating system. Completion Port model can be applied to any kind of IO including the file read /write and serial communication.  

The .NET asynchronous socket programming helper class's Socket provides the similar model.  

BeginReceive
 

.NET framework's Socket class provides BeginReceive method to receive data asynchronously i.e., in an non-blocking manner The BeginReceive method has following signature:

public IAsyncResult BeginReceive( byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state );

The way BeginReceive function works is that you pass the function a buffer , a callback function (delegate)   which will be called whenever data arrives.

The last parameter,  object,   to the BeginReceive can be any class derived from object  ( even null ) .
When the callback function is called it means that the BeginReceive function completed which means that the data has arrived.
The callback function needs to have the following signature:

void AsyncCallback( IAsyncResult ar);

As you can see the callback returns void and is passed in one parameter , IAsyncResult interface , which contains  the status of the asynchronous receive  operation.

The IAsyncResult interface has several properties. The first parameter - AsyncState - is an object which is same as the last parameter that you passed to BeginReceive(). The second  property is AsyncWaitHandle which we will discuss in a moment. The third property indicates whether the receive was really asynchronous or it finished synchronously. The important thing to follow here is that it not necessary for an asynchronous function to always  finish asynchronously - it can complete immediately if the data is already present. Next parameter is IsComplete   which indicates whether the operation has completed or not.

If you look at the signature of the BeginReceive again you will note that the function also returns IAsyncResult. This is interesting. Just now I said that I will talk about the second peoperty of the IAsyncResult  in a moment. Now is that moment. The second parameter is called AsyncWaitHandle.

The AsyncWaitHandle is of type WaitHandle, a class defined in the System.Threading namespace. WaitHandle class encapsulates a Handle (which is a pointer to int or handle ) and provides a way to wait for that handle to become signaled. The class has several static methods like WaitOne ( which is similar to WaitForSingleObject ) WaitAll ( similar to WaitForMultipleObjects with waitAll true ) , WaitAny etc. Also there are overloads of these functions available with timeouts.

Coming back to our discussion of IAsyncResult interface, the handle in AsyncWaitHandle (WaitHandle) is signalled when the receive operation completes. So if we wait on that handle infinitely we will be able to know when the receive completed. This means if we pass that WaitHandle to  a different thread, the different thread can wait on that handle and can notify us of the fact that the data has arrived and so that we can read the data. So you must be wondering if we use this mechanism why would we use callback function. We won't. Thats right. If  we choose to use this mechanism of the WaitHandle then the callback function parameter to the BeginReceive can be null as shown here:

//m_asynResult is  declared of type IAsyncResult and assumming that m_socClient has made a connection.
m_asynResult = m_socClient.BeginReceive(m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None,null,null);
if ( m_asynResult.AsyncWaitHandle.WaitOne () )
{
int iRx = 0 ;
iRx = m_socClient.EndReceive (m_asynResult);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(m_DataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
txtDataRx.Text = txtDataRx.Text + szData;
}

Even though this mechanism will work fine using multiple threads, we will for now stick to our callback mechanism where the system notifies us of the completion of asynchronous operation which is Receive in this case .
Lets say we made the call to BeginReceive and after some time the data arrived and our callback function got called.Now question is where's the data? The data is now available in the buffer that you passed as the first parameter while making call to BeginReceive() method . In the following example the data will be available in m_DataBuffer :

BeginReceive(m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None, pfnCallBack,null);

But before you access the buffer you need to call EndReceive() function on the socket. The EndReceive will return the number of bytes received . Its not legal to access the buffer before calling EndReceive.
To put it all together  look at the following simple code:

byte[] m_DataBuffer = new byte [10];
IAsyncResult m_asynResult;
public AsyncCallback pfnCallBack ;
public Socket m_socClient;
// create the socket...
public void OnConnect()
{
m_socClient = new Socket (AddressFamily.InterNetwork,SocketType.Stream ,ProtocolType.Tcp );
// get the remote IP address...
IPAddress ip = IPAddress.Parse ("10.10.120.122");
int iPortNo = 8221;
//create the end point
IPEndPoint ipEnd = new IPEndPoint (ip.Address,iPortNo);
//connect to the remote host...
m_socClient.Connect ( ipEnd );
//watch for data ( asynchronously )...
WaitForData();
}
public void WaitForData()
{
if ( pfnCallBack == null )
pfnCallBack = new AsyncCallback (OnDataReceived);
// now start to listen for any data...
m_asynResult =
m_socClient.BeginReceive (m_DataBuffer,0,m_DataBuffer.Length,SocketFlags.None, pfnCallBack,null);
}
public void OnDataReceived(IAsyncResult asyn)
{
//end receive...
int iRx = 0 ;
iRx = m_socClient.EndReceive (asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(m_DataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
WaitForData();
}


The OnConnect function makes a connection to the server and then makes a call to WaitForData. WaitForData creates the callback function and makes a call to BeginReceive passing a global buffer and the callback function. When data arrives the OnDataReceive is called and the m_socClient's EndReceive is called which returns the number of bytes received and then the data is copied over to a string and a new call is made to WaitForData which will call BeginReceive again and so on.  This works fine if you have one socket in you application.  

MULTIPLE SOCKETS

Now lets say you have two sockets connecting to either two different servers or same server(which is valid) . One way is to create two different delegates and attach a different delegate to different BeginReceive function. What if you have 3 sockets or for that matter n sockets , this approach of creating multiple delegates does not fit well in such cases. So the solution should be to use only one delegate callback. But then the problem is how do we know what socket completed the operation.

Fortunately there is a better solution. If you look at the BeginReceive function again, the last parameter is  a state is an object. You can pass anything here . And whatever you pass here will be passed back to you later as the part of parameter to the callback function. Actually this object will be passed to you later as a IAsyncResult.AsyncState. So when your callback gets called, you can use this information to identify the socket that completed the operation. Since you can pass any thing to this last parameter, we can pass a class object that contains as much information as we want. For example we can declare a class as follows:

public class CSocketPacket
{
public System.Net.Sockets.Socket thisSocket;
public byte[] dataBuffer = new byte[1024];
}

and call BeginReceive as follows:

CSocketPacket theSocPkt = new CSocketPacket ();
theSocPkt.thisSocket = m_socClient;
// now start to listen for any data...
m_asynResult = m_socClient.BeginReceive (theSocPkt.dataBuffer ,0,theSocPkt.dataBuffer.Length , SocketFlags.None,pfnCallBack,theSocPkt);

and in the callback function we can get the data like this:

public void OnDataReceived(IAsyncResult asyn)
{
  try
  {
CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState ;
//end receive...
int iRx = 0 ;
iRx = theSockId.thisSocket.EndReceive (asyn);
char[] chars = new char[iRx + 1];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(theSockId.dataBuffer, 0, iRx, chars, 0);
System.String szData = new System.String(chars);
txtDataRx.Text = txtDataRx.Text + szData;
WaitForData();
  }
  catch (ObjectDisposedException )
  {
System.Diagnostics.Debugger.Log(0,"1","\nOnDataReceived: Socket has been closed\n");
  }
  catch(SocketException se)
  {
MessageBox.Show (se.Message );
  }
}

To see the whole application download the code and you can see the code.

There is one thing which you may be wondering about. When you call BeginReceive , you have to pass a buffer and the number of bytes to receive. The question here is how big should the buffer be. Well, the answer is it depends. You can have a very small buffer size say, 10 bytes long and if there are 20 bytes ready to be read, then you would require 2 calls to receive the data. On the other hand if you specify the length as 1024 and you know you are always going to receive data in 10-byte  chunks you are unnecessarily wasting memory. So the length depends upon your application.

Server Side

If you have understood whatever I have described so far, you will easily understand the Server part of the socket application. So far we have been talking about a client making connection to a server and sending and receiving data.

On the Server end, the application has to send and receive data. But in addition to adding and receiving data, server has to allow the clients to make connections by listening at some port. Server does not need to know client I.P. addresses. It really does not care where the client is because its not the server but client who is responsible for making connection. Server's responsibility is to manage client connections.

On the server side there has to be one socket called the Listener socket that listens at a specific port number for client connections. When the client makes a  connection, the server needs to accept the connection and then in order for the server to send and receive data from that connected client it needs to talk to that client through the socket that it got when it accepted the connection . Following code illustrates how server listens to the connections and accepts the connection:

public Socket m_socListener;
public void StartListening()
{
try
{
//create the listening socket...
m_socListener = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint ipLocal = new IPEndPoint ( IPAddress.Any ,8221);
//bind to local IP Address...
m_socListener.Bind( ipLocal );
//start listening...
m_socListener.Listen (4);
// create the call back for any client connections...
m_socListener.BeginAccept(new AsyncCallback ( OnClientConnect ),null);
cmdListen.Enabled = false;
}
catch(SocketException se)
{
MessageBox.Show ( se.Message );
}
}

If you look at the above code carefully you will see that its similar to we did in the asynchronous client. First of all the we need to create a listening socket and bind it to a local IP address. Note that we have given Any as the IPAddress . I will explain what it means later. and also we have passed port number as 8221. Next we made a call to Listen function. The 4 is a parameter indicating backlog indicating the maximum length of the queue of pending connections.

Next we made a call to BeginAccept passing it a delegate callback. BeginAccept is a non-blocking method that returns immediately and when a client has made requested a connection, the callback routine is called and you can accept the connection by calling EndAccept. The EndAccept returns a socket object which represents the incoming connection. Here is the code for the callback delegate:

public void OnClientConnect(IAsyncResult asyn)
{
try
{
  m_socWorker = m_socListener.EndAccept (asyn);
WaitForData(m_socWorker);
}
catch(ObjectDisposedException)
{
             System.Diagnostics.Debugger.Log(0,"1","\n OnClientConnection: Socket has been closed\n");
}
catch(SocketException se)
{
MessageBox.Show ( se.Message );
}
}

Here we accept the connection and call WaitForData which in turn calls BeginReceive for the m_socWorker.

If we want to send data  some data to client we use m_socWorker socket for that purpose like this:

Object objData = txtDataTx.Text;
byte[] byData = System.Text.Encoding.ASCII.GetBytes(objData.ToString ());
m_socWorker.Send (byData);

Here is how our client looks like

Here is how our server looks like

That is all there is to the socket programming.


"Network Programming" 카테고리의 다른 글
  • Socket Programming in C# (0)2007/07/27
  • Network Programming in C# (0)2007/07/27
  • IOCP Thread Pooling in C# (0)2007/07/26
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
2007/07/27 09:22 2007/07/27 09:22
Posted by webdizen
Tags C#, Socket
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3099

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/07/27 09:11

Network Programming in C#

출처 : http://www.devarticles.com/c/a/c-sharp/ ··· sharp%2F

Network Programming in C#
(Page 1 of 2 )

Rajesh will now educate C# programmers by demonstrating the correct method of using the Socket class. A must read for those network programmers out there.

The .NET framework provides two namespaces, System.Net and System.Net.Sockets for network programming. The classes and methods of these namespaces help us to write programs, which can communicate across the network. The communication can be either connection oriented or connectionless. They can also be either stream oriented or data-gram based. The most widely used protocol TCP is used for stream-based communication and UDP is used for data-grams based applications. 

The System.Net.Sockets.Socket is an important class from the System.Net.Sockets namespace. A Socket instance has a local and a remote end-point associated with it. The local end-point contains the connection information for the current socket instance. 

There are some other helper classes like IPEndPoint, IPADdress, SocketException etc, which we can use for Network programming. The .NET framework supports both synchronous and asynchronous communication between the client and server. There are different methods supporting for these two types of communication.

A synchronous method is operating in blocking mode, in which the method waits until the operation is complete before it returns. But an asynchronous method is operating in non-blocking mode, where it returns immediately, possibly before the operation has completed.

Dns Class

The System.net namespace provides this class, which can be used to creates and send queries to obtain information about the host server from the Internet Domain Name Service (DNS). Remember that in order to access DNS, the machine executing the query must be connected to a network. If the query is executed on a machine, that does not have access to a domain name server, a System.Net.SocketException is thrown. All the members of this class are static in nature. The important methods of this class are given below. 

public static IPHostEntry GetHostByAddress(string address)

Where address should be in a dotted-quad format like "202.87.40.193". This method returns an IPHostEntry instance containing the host information. If DNS server is not available, the method returns a SocketException. 

public static string GetHostName()

This method returns the DNS host name of the local machine.

In my machine Dns.GetHostName() returns vrajesh which is the DNS name of my machine. 

public static IPHostEntry Resolve(string hostname)

This method resolves a DNS host name or IP address to a IPHostEntry instance. The host name should be in a dotted-quad format like 127.0.01 or www.microsoft.com. 

IPHostEntry Class

This is a container class for Internet host address information. This class makes no thread safety guarantees. The following are the important members of this class. 

AddressList Property

Gives an IPAddress array containing IP addresses that resolve to the host name. 

Aliases Property

Gives a string array containing DNS name that resolves to the IP addresses in AddressList property. 

The following program shows the application of the above two classes.

using System;
using System.Net;
using System.Net.Sockets;
class MyClient
{
           public static void Main()
           {
                       IPHostEntry IPHost = Dns.Resolve("www.hotmail.com");
                       Console.WriteLine(IPHost.HostName);
                       string []aliases = IPHost.Aliases;
                       Console.WriteLine(aliases.Length);
                       IPAddress[] addr = IPHost.AddressList;
                       Console.WriteLine(addr.Length);
                       for(int i= 0; i < addr.Length ; i++)
                       {
                                   Console.WriteLine(addr[i]);
                       }
           }
}

IPEndPoint Class

This class is a concrete derived class of the abstract class EndPoint. The IPEndPoint class represents a network end point as an IP address and a port number. There is couple of useful constructors in this class: 

IPEndPoint(long addresses, int port)
IPEndPoint (IPAddress addr, int port) 
IPHostEntry IPHost = Dns.Resolve("www.c-sharpcorner.com");
Console.WriteLine(IPHost.HostName);
string []aliases = IPHost.Aliases;
IPAddress[] addr = IPHost.AddressList;
Console.WriteLine(addr[0]);
EndPoint ep = new IPEndPoint(addr[0],80);


Network Programming in C# - Part 2
(Page 2 of 2 )

Socket Programming: Synchronous Clients 

The steps for creating a simple synchronous client are as follows.

  1. Create a Socket instance.
  2. Connect the above socket instance to an end-point.
  3. Send or Receive information.
  4. Shutdown the socket
  5. Close the socket 

The Socket class provides a constructor for creating a Socket instance. 

public Socket (AddressFamily af, ProtocolType pt, SocketType st)

Where AddressFamily, ProtocolType and SocketTYpe are the enumeration types declared inside the Socket class.

The AddressFamily member specifies the addressing scheme that a socket instance must use to resolve an address. For example AddressFamily.InterNetwork indicates that an IP version 4 addresses is expected when a socket connects to an end point. 

The SocketType parameter specifies the socket type of the current instance. For example SocketType.Stream indicates a connection-oriented stream and SocketType.Dgram indicates a connectionless stream.

The ProtocolType parameter specifies the ptotocol to be used for the communication. For example ProtocolType.Tcp indicates that the protocol used is TCP and ProtocolType.Udp indicates that the protocol using is UDP. 

public Connect (EndPoint ep)

The Connect() method is used by the local end-point to connect to the remote end-point. This method is used only in the client side. Once the connection has been established the Send() and Receive() methods can be used for sending and receiving the data across the network. 

The Connected property defined inside the class Socket can be used for checking the connection. We can use the Connected property of the Socket class to know whether the current Socket instance is connected or not. A property value of true indicates that the current Socket instance is connected.

IPHostEntry IPHost = Dns.Resolve("www.c-sharpcorner.com");
Console.WriteLine(IPHost.HostName);
string []aliases = IPHost.Aliases;
IPAddress[] addr = IPHost.AddressList;
Console.WriteLine(addr[0]);
EndPoint ep = new IPEndPoint(addr[0],80);
Socket sock = new                             
Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
sock.Connect(ep);
if(sock.Connected)
Console.WriteLine("OK");

The Send() method of the socket class can be used to send data to a connected remote socket.

public int Send (byte[] buffer, int size, SocketFlags flags)

Where byte[] parameter storing the data to send to the socket, size parameter containing the number of bytes to send across the network. The SocketFlags parameter can be a bitwise combination of any one of the following values defined in the System.Net.Sockets.SocketFlags enumerator.
        
SocketFlags.None
SocketFlags.DontRoute
SocketFlags.OutOfBnd
 

The method Send() returns a System.Int32 containing the number of bytes send.Remember that there are other overloaded versions of Send() method as follows. 

public int Send (byte[] buffer,  SocketFlags flags)
public int Send (byte[] buffer)
public int Send (byte[] buffer,int offset, int size, SocketFlags flags)
 

The Receive() method can be used to receive data from a socket.        

public int Receive(byte[] buffer, int size, SocketFlags flags) 

Where byte[] parameter storing the data to send to the socket, size parameter containing the number of bytes to send across the network. The SocketFlags parameter can be a bitwise combination of any one of the following values defined in the System.Net.Sockets.SocketFlags enumerator explained above. 

The overloaded versions of Receive() methods are shown below. 

public int Receive (byte[] buffer,  SocketFlags flags)
public int Receive (byte[] buffer)
public int Receive (byte[] buffer,int offset, int size, SocketFlags flags)
 

When the communication across the sockets is over, the connection between the sockets can be terminated by invoking the method ShutDown() 

public void ShutDown(SocketShutdown how)

Where ‘how’ is one of the values defined in the SocketSHutdown enumeration. The value SoketShutdown.Send means that the socket on the other end of the connection is notified that the current instance would not send any more data.

The value SoketShutdown.Receive means that the socket on the other end of the connection is notified that the current instance will not receive any more data and the value SoketShutdown.Both means that both the action are not possible. 

Remember that the ShutDown() method must be called before the Close(0 method to ensure that all pending data is sent or received. 

A socket can be closed by invoking the method Close(). 

public void Close()

This method closes the current instance and releases all managed and un-managed resources allocated by the current instance. This method internally calls the Dispose() method with an argument of ‘true’ value, which frees both managed and un-managed resources used by the current instance. 

protected virtual void Dispose(bool)

The above method closes the current instance and releases the un-managed resources allocated by the current instance and exceptionally release the managed resources also. An argument value of ‘true’ releases both managed and un-managed resources and a value of ‘false’ releases only un-managed resources. 

The source code for a simple synchronous client by using the sockets is show below. The following program can send an HTTP request to a web server and can read the response from the web server. 

using System;
using System.Net;
using System.Net.Sockets;
using System.Text; 
class MyClient
{
           public static void Main()
           {
                       IPHostEntry IPHost = Dns.Resolve("
www.google.com
");
                       Console.WriteLine(IPHost.HostName);
                       string []aliases = IPHost.Aliases; 
                       IPAddress[] addr = IPHost.AddressList;
                       Console.WriteLine(addr[0]);
                       EndPoint ep = new IPEndPoint(addr[0],80); 
  Socket sock = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                       sock.Connect(ep);
                       if(sock.Connected)
                        Console.WriteLine("OK");
                       Encoding ASCII = Encoding.ASCII;
                       string Get = "GET / HTTP/1.1\r\nHost: " + "www. google.com" +
                       "\r\nConnection: Close\r\n\r\n";
                       Byte[] ByteGet = ASCII.GetBytes(Get);
                       Byte[] RecvBytes = new Byte[256];
                       sock.Send(ByteGet, ByteGet.Length, 0);
                       Int32 bytes = sock.Receive(RecvBytes, RecvBytes.Length, 0);
                       Console.WriteLine(bytes);
                       String strRetPage = null;
                       strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);
                       while (bytes > 0)
                       {
                                   bytes = sock.Receive(RecvBytes, RecvBytes.Length, 0);
                                   strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);
                                   Console.WriteLine(strRetPage );
                       }
                       sock.ShutDown(SocketShutdown.Both);
                       sock.Close();
           }
}

"Network Programming" 카테고리의 다른 글
  • Socket Programming in C# (0)2007/07/27
  • Network Programming in C# (0)2007/07/27
  • IOCP Thread Pooling in C# (0)2007/07/26
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
2007/07/27 09:11 2007/07/27 09:11
Posted by webdizen
Tags C#, Network, Socket
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3096

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/07/26 14:07

IOCP Thread Pooling in C#

출처 : http://www.devarticles.com/c/a/C-Sharp/IOCP-Thread-Pooling-in-C-sharp-Part-I/

IOCP Thread Pooling in C# - Part I
(Page 1 of 3 )

This is the first part of William's two part series on thread pooling in C#. By importing a dll file for IOCP thread support.

When building server based applications in C#, it is important to have the ability to create thread pools.  Thread pools allow our server to queue and perform work in the most efficient and scalable way possible.  Without thread pooling we are left with two options. 

The first option is to perform all of the work on a single thread.  The second option is to spawn a thread every time some piece of work needs to be done.  For this article, work is defined as an event that requires the processing of code.  Work may or may not be associated with data, and it is our job to process all of the work our server receives in the most efficient and fastest way possible.

As a general rule, if you can accomplish all of the work required with a single thread, then only use a single thread.   Having multiple threads performing work at the same time does not necessarily mean our application is getting more work done, or getting work done faster. This is true for many reasons. 

For example, if you spawn multiple threads which attempt to access the same resource bound to a synchronization object, like a monitor object, these threads will serialize and fall in line waiting for the resource to become available.  As each thread tries to access the resource, it has the potential to block, and wait for the thread that owns the resource to release the resource. 

At that point, these waiting threads are put to sleep, and not getting any work done.  In fact, these waiting threads have caused more work for the operating system to perform.  Now the operating system must task another thread to perform work, and then determine which thread, waiting for the resource, may access the resource next, once it becomes available. 

If the threads that need to perform work are sleeping, because they are waiting for the resource to become available, we have actually created a performance problem.  In this case it would be more efficient to queue up this work and have a single thread process the queue. 

Threads that start waiting for a resource before other threads, are not guaranteed to be given the resource first.  In diagram A, thread 1 requests access to the resource before thread 2, and thread 2 requests access to the resource before thread 3.  The operating system however decides to give the resource to thread 1 first, then thread 3, and then thread 2.  This scenario causes work to be performed in an undetermined order.  The possible issues are endless when dealing with multi-threaded applications.


If work received can be performed independent of each other, we could always spawn a thread for processing that piece of work.  The problem here is that an operating system like Windows has severe performance problems when a large number of threads are created or running at the same time, waiting to have access to the CPU. 

The Windows operating system needs to manage all of these threads, and compared to the UNIX operating system, it just doesn’t hold up.  If large amounts of work are issued to the server, this model will most likely cause the Windows operating system to become overloaded.  System performance will degrade drastically.
<>

This article is a case study comparing thread performance between Windows NT and Solaris.

http://www.usenix.org/publications/libr ··· tta.html

In the .NET framework, the “System.Threading” namespace has a ThreadPool class.  Unfortunately, it is a static class and therefore our server can only have a single thread pool.  This isn’t the only issue.  The ThreadPool class does not allow us to set the concurrency level of the thread pool. 

The concurrency level is the most important setting when configuring a thread pool.  The concurrency level defines how many threads in the pool may be in an “active state” at the same time.  If we set this parameter correctly, we will have the most efficient, performance enhanced thread pool for the work being processed.

Imagine we have a thread pool with 4 threads and a concurrency level of 1. Then, three pieces of work are queued up for processing in the pool. Since the concurrency level for the thread pool is 1, only a single thread from the pool is activated and given work from the queue.  Even though there are two pieces of work queued up, no other threads are activated.  This is because the concurrency level is set to 1.  If the concurrency level was set to 2, then another thread would have been activated immediately and given work from the queue.  In diagram B we have thread 1 running and all of the other threads sleeping with two pieces of work queued.

So the question exists, why have more than 1 thread in the pool if the concurrency level is set to 1?  If thread 1 in diagram B ever goes to sleep before it completes its work, another thread from the pool will be activated.  When thread 1 goes to sleep, there are 0 threads “active” in the pool and it is ok to activate a new thread based on the concurrency level.  In diagram C, we now have thread 1 sleeping and thread 4 running with one piece of work queued.

Eventually, thread 1 will wake up, and it is possible for thread 4 to still be active.  We have 2 threads active in the pool, even though the concurrency level is set to 1.  In diagram D, we now have thread 1 and thread 4 running and one piece of work still queued.

The last piece of work in the queue will need to wait until both threads return to a sleeping state.  This is because the concurrency level is set to 1.  As we can see, even though the concurrency level restricts the number of active threads in the pool at any given time, we could have more active threads then the concurrency level allows.  It all depends on the state of the threads in the pool and how fast the threads can complete the work they are processing.

A good rule of thumb is to set the concurrency level to match the number of CPU’s in the system.  If the machine our server is running on only has one CPU, then only one thread can be executing at any given time. It will require a task swap to have another thread get CPU time.  We want to reduce the number of active threads at any given time to maximize performance.  This also leads to scalability.  As the number of CPU’s increase, we can increase the concurrency level because there is a CPU to execute that thread.  This is a general rule and is always a good starting point for configuring our thread pools.

The bottom line is, if the CPU is available, and there is work to perform, activate a thread.  If the CPU is not available, do not activate a thread.  One other thing, we need to be careful that we don’t cause a situation where the threads in the pool are constantly being put to sleep for long periods of time during the processing of work.  This may cause all of the threads in the pool to constantly be in an active state, defeating the efficiency of the pool and the performance of the server.

The remaining scope of this article will show you how to add IOCP thread pools to your C# server based applications.  How to configure the thread pools for your specific application will not be covered.  It is suggested to use the general rules as discussed.

This Win32 API call is used to create an IOCP thread pool.  The first argument will always be set to INVALID_HANDLE_VALUE, which is 0xFFFFFFFF.  This tells the operating system this IOCP thread pool is not linked to a device.  The second argument will always be set to 0. There is no existing IOCP thread pool because we are creating this for the first time.  The third argument will always be null.  IOCP Thread Pooling in C# - Part I - The Article
(Page 2 of 3 )

System Requirements

A basic understanding of C# is required to follow through the examples and the classes.  Basic concepts of type, properties, threading, synchronization, and delegates are required.

Defining the Problem

IOCP thread support has not been made available to C# developers through the “System.Threading” namespace.  We need to access the Win32 API calls from the Kernel32.dll.  This requires us to write unsafe code.  This is really not a problem, but something that needs to be discussed.  Let’s take a look at the Win32 API calls we need to implement an IOCP thread pool.

[DllImport("Kernel32", CharSet=CharSet.Auto)]
private unsafe static extern UInt32 CreateIoCompletionPort(UInt32 hFile, UInt32 hExistingCompletionPort, UInt32* puiCompletionKey, UInt32 uiNumberOfConcurrentThreads);

We do not require a key because we have not associated this IOCP thread pool with a device.  The last argument is the important argument.  Here we define the concurrency level of the thread pool.  If we pass a 0 for this argument the operating system will set the concurrency level to match the number of CPU’s in the machine. 

This option gives us our best chance to be scalable and take advantage of the number of CPU’s present in the machine.  This API call will return a handle to the newly created IOCP thread pool.  If the API call fails, it will return null.

[DllImport("Kernel32", CharSet=CharSet.Auto)]
private unsafe static extern Boolean CloseHandle(UInt32 hObject);

This Win32 API call is used to close our thread pool.  The only argument is the handle to the IOCP thread pool.  This API call will return TRUE or FALSE if the handle can not be closed.

[DllImport("Kernel32", CharSet=CharSet.Auto)]
private unsafe static extern Boolean PostQueuedCompletionStatus(UInt32 hCompletionPort, UInt32 uiSizeOfArgument, UInt32* puiUserArg, OVERLAPPED* pOverlapped);

This Win32 API call is used to post work in the IOCP thread pool queue. Other threads in our application will make this Win32 API call.  The first argument is the handle to the IOCP thread pool.  The second argument is the size of the data we are posting to the queue.  The third argument is a value or a reference to an object or data structure we are posting to the queue.  The last argument will always be null. The following diagram shows how the data is associated with the posted work.

In diagram E, we have two threads actively processing posted work and one piece of work on the queue waiting for its data to be processed.  The thing to note here is that each piece of work was given a reference to its specific data.  I am calling this variable pData to help describe what is happening in the IOCP thread pool.  The actual name or structure of this variable is undocumented.

When we make this API call in a C++ application, we can pass the address of any object in memory we wish, as in diagram E.  In C#, we don’t have the same luxury because of the managed heap.  The managed heap is a contiguous region of address space that contains all of the memory allocated for reference variables.  The heap maintains a pointer that indicates where the next object is to be allocated, and all allocations are contiguous from that point.  This is much different from the C-runtime heap. 

The C-runtime heap uses a link list of data structures to reference available memory blocks.  For the C-runtime heap to allocate memory, it must walk through the link list until a large enough block of free memory is found.  Then the free block of memory must be resized, and the link list adjusted. 

If objects are allocated consecutively in a C++ application, those objects could be allocated anywhere on the heap.  This can never happen with the managed heap.  Objects that are allocated consecutively in a C# application will always be allocated consecutively on the managed heap.  The catch is that the managed heap must be compacted to guarantee the heap does not run out of memory.  That is the job of garbage collection.

For more information on garbage collection, try these links:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpguide/html/cpconautomaticmemorymanagement.asp

http://msdn.microsoft.com/msdnmag/issues/1100/GCI/default.aspx

http://msdn.microsoft.com/msdnmag/issues/1200/GCI2/default.aspx

In diagram F, we have allocated four objects on the managed heap.  Imagine that the managed heap has allocated memory for these objects at address FDEO, FDDO, FDCO, and FDBO.  This would mean the value of pClass1 is FDEO, the value of pClass2 is FDDO, the value of pClass3 is FDCO, and the value of pClass4 is FDBO. 

MyClass pClass1 = new MyClass();
MyClass pClass2 = new MyClass();
MyClass pClass3 = new MyClass();
MyClass pClass4 = new MyClass();


Now we write the following code.

pClass2 = null;

Diagram G shows what happens to the managed heap after garbage collection takes place and the managed heap is compacted.

The Class 2 object has been removed from the managed heap and the Class3 and Class 4 objects have been moved.  Now the value of pClass3 is FDDO and the value of pClass4 is FDCO.  The value that the pointer points to has changed.  The garbage collection process changes the values of all reference variables to make sure they are pointing to the correct objects after the managed heap is compacted.

So what does this mean for our IOCP thread pool implementation?  If we pass the reference of a managed object as the data for the work, there is a chance the reference is no longer valid when a thread in the pool is chosen to work on the data.

In diagram H, we have passed a reference to the Class 3 object as the data for the work posted to the IOCP thread pool.  This object is at address FDCO.  Before the work is given to thread 1, the Class 2 object is marked for deletion.  Then the garbage collection process runs, and the managed heap is compacted.  Now in diagram I, the work has been given to thread 1 for processing.  The value of pData is still FDCO, but Class 3 is no longer at address FDCO, it is at address FDDO.  The thread will perform the work, but using Class 4 instead of Class 3.

The garbage collection process can not change the value of pData, as it does with other variables, because this variable is not a managed variable.  It is a variable owned by the IOCP thread pool and exists outside the scope of the CLR.  The garbage collector has no knowledge of this variable or access to this variable.  The variable is set during the unsafe call to PostQueuedCompletionStatus.

Unfortunately, pinning the objects we want to pass as the data for the work posted to the IOCP thread pool is not a possible solution.  Pinning provides the ability to prevent an object from being moved on the manage heap during the garbage collection process.  We can not pin these objects because there is no way to pin an object in one thread and unpin the object in a different thread.  To pin an object, we need to use the fixed keyword.  This keyword can only be used in the context of a single method. Here is a quick example of pinning.

Int32 iArray = new Int32[5] {12, 34, 56, 78, 90};
unsafe
{
  fixed (Int32* piArray = iArray)
  {
   // Do Something
  }
}

The safest thing we can do is pass a value to the IOCP thread pool.  This value could be the index from a managed array, containing a reference to an object on the managed heap.   If the garbage collection process does compact the heap, the index values of the array will not change. In diagram J and K, we can see one way to properly pass data for the work posted to the IOCP thread pool.  After the garbage collection process compacts the heap, the values of pData change, but the index positions to the pData variables do not change.

[DllImport("Kernel32", CharSet=CharSet.Auto)]
private static extern Boolean GetQueuedCompletionStatus(UInt32 hCompletionPort, UInt32* pSizeOfArgument, UInt32* puiUserArg, OVERLAPPED** ppOverlapped, UInt32 uiMilliseconds);

The final Win32 API call is used to add threads to the IOCP thread pool. Any thread that makes this Win32 API call will become part of the IOCP thread pool.  This is a blocking call and the method will return when the IOCP thread pool chooses the thread to perform work. 

The first argument is the handle to the IOCP thread pool.  The second argument is the size of the data associated with the work.  This value was provided when the work was posted.  The third argument is the data value or data reference associated with the work.  This value was provided when the work was posted.  The forth argument is the address to a pointer of type OVERLAPPED. 

This address is returned after the call.  The last argument is the time in milliseconds the thread should wait to be activated to perform work. We will always pass INFINITE or 0xFFFFFFFF.

These are the Win32 API calls we need to add IOCP thread pool support to our C# server based applications.  We need to encapsulate these Win32 API calls using .NET threads and minimize the sections of unsafe code.  We need to prevent the application developer from passing a reference variable into the IOCP thread pool, by restricting them to passing only integer values.

IOCP Thread Pooling in C# - Part I - What to Expect in Part 2
(Page 3 of 3 )

In part II of this article, we will build a class that encapsulates a single IOCP thread pool.  The application developer will be able to instantiate as many thread pools as he wishes. 

During construction, the application developer will be able to: set the concurrency level of the thread pool, set the minimum and maximum number of threads in the pool, and will be able to provide a method to be called when work posted to the thread pool needs to be processed. The application developer will also be able to post work with data into the IOCP thread pool.

IOCP Thread Pooling in C# - Part II
(Page 1 of 3 )

In part 2, William will continue to explain how the create a class that will handle threads using a IOCP Thread Pool.

Defining the Solution

We will build a class that encapsulates a single IOCP thread pool.  The application developer will be able to instantiate as many thread pools as he wishes.  During construction, the application developer will be able to: set the concurrency level of the thread pool, set the minimum and maximum number of threads in the pool, and will be able to provide a method to be called when work posted to the thread pool needs to be processed.  The application developer will also be able to post work with data into the IOCP thread pool.

Component Design and Coding

Start by adding a new class to your C# project.  Remove all of the code provided by the Visual Studio .NET wizard.  Then add the following namespaces.  The System.Runtime.InteropServices namespace is required to access the Win32 API methods from the Kernel32 DLL. 

using System;
using System.Threading;
using System.Runtime.InteropServices;

Don’t forget to change the project properties to allow unsafe code blocks. This can be done by opening the project properties and selecting the Configuration Properties.  Under the Build / Code Generation section you will see “Allow unsafe code blocks”.  Set this to true.

Next add the namespace.   You will notice that I have defined a two level namespace.  This is great when you are building a class library with many different classes.

namespace Continuum.Threading
{

The PostQueuedCompletionStatus and GetQueuedCompletionStats Win32 API methods both require a pointer to the Win32 OVERLAPPED structure. Because this structure will be used by the unsafe Win32 API call, we need to make sure the structure is aligned exactly the same way it would be in our C++ applications.  This can be accomplished by using the StructLayout attribute.  By setting the attribute to “LayoutKind.Sequential”, the structure will be aligned based on the same rules as the C++ compiler.

The structure requires members that are pointers.  The only way to add pointers to this structure is to use the unsafe keyword.  We can still use the FCL types when defining the structure.  This is very important because we can make sure the structure is identical to the C++ version.

// Structures
  //==========================================
  /// <summary> This is the WIN32 OVERLAPPED structure </summary>
  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
  public unsafe struct OVERLAPPED
  {
   UInt32* ulpInternal;
   UInt32* ulpInternalHigh;
   Int32   lOffset;
   Int32   lOffsetHigh;
   UInt32  hEvent;
  }

Now it is time to define the IOCP thread pool class.  I am using the keyword sealed in the definition of this class.  The sealed keyword tells the compiler that this class can not be inherited.  If you know there is no reason for a class to be inherited, use the sealed keyword.  Certain run-time optimizations are enabled for the class when the sealed keyword is used.

// Classes
  //============================================
  /// <summary> This class provides the ability to create a thread pool to manage work.  The
  ///           class abstracts the Win32 IOCompletionPort API so it requires the use of
  ///           unmanaged code.  Unfortunately the .NET framework does not provide this functionality </summary>
  public sealed class IOCPThreadPool
  {

The first section of the IOCP thread pool class is the Win32 function prototypes.  These are the same ones described earlier.

  // Win32 Function Prototypes
   /// <summary> Win32Func: Create an IO Completion Port Thread Pool </summary>
   [DllImport("Kernel32", CharSet=CharSet.Auto)]
  private unsafe static extern UInt32 CreateIoCompletionPort(UInt32 hFile, UInt32 hExistingCompletionPort, UInt32* puiCompletionKey, UInt32 uiNumberOfConcurrentThreads);

   /// <summary> Win32Func: Closes an IO Completion Port Thread Pool </summary>
   [DllImport("Kernel32", CharSet=CharSet.Auto)]
   private unsafe static extern Boolean CloseHandle(UInt32 hObject);

   /// <summary> Win32Func: Posts a context based event into an IO Completion Port Thread Pool </summary>
   [DllImport("Kernel32", CharSet=CharSet.Auto)]
  private unsafe static extern Boolean PostQueuedCompletionStatus(UInt32 hCompletionPort, UInt32 uiSizeOfArgument, UInt32* puiUserArg, OVERLAPPED* pOverlapped);

   /// <summary> Win32Func: Waits on a context based event from an IO Completion Port Thread Pool.
   ///           All threads in the pool wait in this Win32 Function </summary>
   [DllImport("Kernel32", CharSet=CharSet.Auto)]
  private unsafe static extern Boolean GetQueuedCompletionStatus(UInt32 hCompletionPort, UInt32* pSizeOfArgument, UInt32* puiUserArg, OVERLAPPED** ppOverlapped, UInt32 uiMilliseconds);

The next section is the constants section.  Here we need to define the Win32 constants required for the Win32 API calls we are going to make later.

// Constants
   /// <summary> SimTypeConst: This represents the Win32 Invalid Handle Value Macro </summary>
   private const UInt32 INVALID_HANDLE_VALUE = 0xffffffff;

   /// <summary> SimTypeConst: This represents the Win32 INFINITE Macro </summary>
   private const UInt32 INIFINITE = 0xffffffff;

   /// <summary> SimTypeConst: This tells the IOCP Function to shutdown </summary>
   private const Int32 SHUTDOWN_IOCPTHREAD = 0x7fffffff;

The delegate function type section is where we define any delegate functions.  We need one delegate function type to define the signature of the function we will call when work needs to be processed.

  // Delegate Function Types
   /// <summary> DelType: This is the type of user function to be supplied for the thread pool </summary>
   public delegate void USER_FUNCTION(Int32 iValue);

These private properties are required to maintain the application developer’s settings.  The most interesting property is the GetUserFunction property.  This property contains a reference to a method supplied by the application developer.  We will use this property to call the application developers method.

  // Private Properties
   private UInt32 m_hHandle;
     /// <summary> SimType: Contains the IO Completion Port Thread Pool handle for this instance </summary>
     private UInt32 GetHandle { get { return m_hHandle; } set { m_hHandle = value; } }

   private Int32 m_uiMaxConcurrency;
     /// <summary> SimType: The maximum number of threads that may be running at the same time </summary>
     private Int32 GetMaxConcurrency { get { return m_uiMaxConcurrency; } set { m_uiMaxConcurrency = value; } }

   private Int32 m_iMinThreadsInPool;
     /// <summary> SimType: The minimal number of threads the thread pool maintains </summary>
     private Int32 GetMinThreadsInPool { get { return m_iMinThreadsInPool; } set { m_iMinThreadsInPool = value; } }

   private Int32 m_iMaxThreadsInPool;
     /// <summary> SimType: The maximum number of threads the thread pool maintains </summary>
     private Int32 GetMaxThreadsInPool { get { return m_iMaxThreadsInPool; } set { m_iMaxThreadsInPool = value; } }

   private Object m_pCriticalSection;
     /// <summary> RefType: A serialization object to protect the class state </summary>
     private Object GetCriticalSection { get { return m_pCriticalSection; } set { m_pCriticalSection = value; } }

   private USER_FUNCTION m_pfnUserFunction;
     /// <summary> DelType: A reference to a user specified function to be call by the thread pool </summary>
     private USER_FUNCTION GetUserFunction { get { return m_pfnUserFunction; } set { m_pfnUserFunction = value; } }
  
   private Boolean m_bDisposeFlag;
     /// <summary> SimType: Flag to indicate if the class is disposing </summary>
     private Boolean IsDisposed { get { return m_bDisposeFlag; } set { m_bDisposeFlag = value; } }
 
These public properties are used to determine if new threads need to be added to the thread pool.  These properties also provide statistical data about the thread pool.  Here we use the Interlocked class to provide serialization when we increment or decrement these properties.  This is the least expensive way to perform serialization.

  // Public Properties
   private Int32 m_iCurThreadsInPool;
     /// <summary> SimType: The current number of threads in the thread pool </summary>
     public Int32 GetCurThreadsInPool { get { return m_iCurThreadsInPool; } set { m_iCurThreadsInPool = value; } }
     /// <summary> SimType: Increment current number of threads in the thread pool </summary>
     private Int32 IncCurThreadsInPool() { return Interlocked.Increment(ref m_iCurThreadsInPool); }
     /// <summary> SimType: Decrement current number of threads in the thread pool </summary>
     private Int32 DecCurThreadsInPool() { return Interlocked.Decrement(ref m_iCurThreadsInPool); }
   private Int32 m_iActThreadsInPool;
     /// <summary> SimType: The current number of active threads in the thread pool </summary>
     public Int32 GetActThreadsInPool { get { return m_iActThreadsInPool; } set { m_iActThreadsInPool = value; } }
     /// <summary> SimType: Increment current number of active threads in the thread pool </summary>
     private Int32 IncActThreadsInPool() { return Interlocked.Increment(ref m_iActThreadsInPool); }
     /// <summary> SimType: Decrement current number of active threads in the thread pool </summary>
     private Int32 DecActThreadsInPool() { return Interlocked.Decrement(ref m_iActThreadsInPool); }
   private Int32 m_iCurWorkInPool;
     /// <summary> SimType: The current number of Work posted in the thread pool </summary>
     public Int32 GetCurWorkInPool { get { return m_iCurWorkInPool; } set { m_iCurWorkInPool = value; } }
     /// <summary> SimType: Increment current number of Work posted in the thread pool </summary>
     private Int32 IncCurWorkInPool() { return Interlocked.Increment(ref m_iCurWorkInPool); }
     /// <summary> SimType: Decrement current number of Work posted in the thread pool </summary>
     private Int32 DecCurWorkInPool() { return Interlocked.Decrement(ref m_iCurWorkInPool); }

The constructor method does several things.  The class state is initialized and then the IOCP thread pool is created with a call to the CreateIoCompletionPort method.  Notice the method call is within the scope of the unsafe keyword.  This is required because we are passing pointers into the Win32 API call. 

The last thing we do is create the minimal number of threads specified by the application developer.  Notice we use the .NET threading classes to create the threads.  We do not need to use the unsafe CreateThread method.  One might think we need to because these threads will be calling the GetQueuedCompletionStatus Win32 API method.

   // Constructor, Finalize, and Dispose
   //***********************************************
   /// <summary> Constructor </summary>
   /// <param name = "iMaxConcurrency"> SimType: Max number of running threads allowed </param>
   /// <param name = "iMinThreadsInPool"> SimType: Min number of threads in the pool </param>
   /// <param name = "iMaxThreadsInPool"> SimType: Max number of threads in the pool </param>
   /// <param name = "pfnUserFunction"> DelType: Reference to a function to call to perform work </param>
   /// <exception cref = "Exception"> Unhandled Exception </exception>
  public IOCPThreadPool(Int32 iMaxConcurrency, Int32 iMinThreadsInPool, Int32 iMaxThreadsInPool, USER_FUNCTION pfnUserFunction)
   {
     try
     {
       // Set initial class state
       GetMaxConcurrency   = iMaxConcurrency;
       GetMinThreadsInPool = iMinThreadsInPool;
       GetMaxThreadsInPool = iMaxThreadsInPool;
       GetUserFunction     = pfnUserFunction;
       // Init the thread counters
       GetCurThreadsInPool = 0;
       GetActThreadsInPool = 0;
       GetCurWorkInPool    = 0;
       // Initialize the Monitor Object
       GetCriticalSection = new Object();
       // Set the disposing flag to false
       IsDisposed = false;
       unsafe
       {
         // Create an IO Completion Port for Thread Pool use
         GetHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, null, (UInt32) GetMaxConcurrency);
       }
       // Test to make sure the IO Completion Port was created
       if (GetHandle == 0)
         throw new Exception("Unable To Create IO Completion Port");
       // Allocate and start the Minimum number of threads specified
       Int32 iStartingCount = GetCurThreadsInPool;
       ThreadStart tsThread = new ThreadStart(IOCPFunction);
       for (Int32 iThread = 0; iThread < GetMinThreadsInPool; ++iThread)
       {
         // Create a thread and start it
         Thread thThread = new Thread(tsThread);
         thThread.Name = "IOCP " + thThread.GetHashCode();
         thThread.Start();
         // Increment the thread pool count
         IncCurThreadsInPool();
       }
     }
     catch
     {
       throw new Exception("Unhandled Exception");
     }
   }

The finalize method is only required to guarantee the IOCP thread pool handle is closed.  As a general rule, if a class allocates a resource outside the scope of the .NET framework, a finalize method is required else do not add a finalize method.  A finalize method will cause the garbage collection process to spend more time trying to release the memory for the object.

   //***********************************************
   /// <summary> Finalize called by the GC </summary>
   ~IOCPThreadPool()
   {
     if (!IsDisposed)
       Dispose();
   }

The dispose method will not return until all of the threads in the pool have been terminated.  We can’t use the Abort method to kill the threads in the pool because any thread blocked, via the call to the GetQueuedCompletionStatus Win32 API method, will not respond to the Abort message. 

The GetQueuedCompletionStatus Win32 API method will cause the thread to run outside the scope of the CLR and the .NET framework will lose access to the thread.  So what we do is post work into the IOCP thread pool.  We pass the SHUTDOWN_IOCPTHREAD data when we post the work.  This will tell the thread to terminate.  Then, we wait in a spin lock, until all of the threads have terminated.  The last thing is to close the IOCP thread pool.

   //**********************************************
   /// <summary> Called when the object will be shutdown.  This
   ///           function will wait for all of the work to be completed
   ///           inside the queue before completing </summary>
   public void Dispose()
   {
     try
     {
       // Flag that we are disposing this object
       IsDisposed = true;
       // Get the current number of threads in the pool
       Int32 iCurThreadsInPool = GetCurThreadsInPool;
       // Shutdown all thread in the pool
       for (Int32 iThread = 0; iThread < iCurThreadsInPool; ++iThread)
       {
         unsafe
         {
           bool bret = PostQueuedCompletionStatus(GetHandle, 4, (UInt32*) SHUTDOWN_IOCPTHREAD, null);
         }
       }
       // Wait here until all the threads are gone
       while (GetCurThreadsInPool != 0) Thread.Sleep(100);
       unsafe
       {
         // Close the IOCP Handle
         CloseHandle(GetHandle);
       }
     }
     catch
     {
     }
   }

The only private method is the IOCPFunction method.  This method is spawned as a thread and is made part of the IOCP thread pool by calling the GetQueuedCompletionStatus Win32 API method.  When the GetQueuedCompletionStatus Win32 API method returns, we check to make sure we are not being asked to shutdown the thread.  The third argument is the data associated with the posted work.  If the data is not SHUTDOWN_IOCPTHREAD, then real work has been posted into the IOCP thread pool and this thread has been chosen to process the work. 

The application developer’s supplied user function is called since the application developer is the only one who knows what needs to be done. Once that is complete, the method checks if a new thread should be added to the pool.  This is done by reviewing the number of active threads in the pool.

   // Private Methods
   //*******************************************
   /// <summary> IOCP Worker Function that calls the specified user function </summary>
   private void IOCPFunction()
   {
     UInt32 uiNumberOfBytes;
     Int32  iValue;
     try
     {
       while (true)
       {
         unsafe
         {
           OVERLAPPED* pOv;
           // Wait for an event
           GetQueuedCompletionStatus(GetHandle, &uiNumberOfBytes, (UInt32*) &iValue, &pOv, INIFINITE);
         }
         // Decrement the number of events in queue
         DecCurWorkInPool();
         // Was this thread told to shutdown
         if (iValue == SHUTDOWN_IOCPTHREAD)
           break;
         // Increment the number of active threads
         IncActThreadsInPool();
         try
         {
           // Call the user function
           GetUserFunction(iValue);
         }
         catch
         {
         }
         // Get a lock
         Monitor.Enter(GetCriticalSection);
         try
         {
           // If we have less than max threads currently in the pool
           if (GetCurThreadsInPool < GetMaxThreadsInPool)
           {
             // Should we add a new thread to the pool
             if (GetActThreadsInPool == GetCurThreadsInPool)
             {
               if (IsDisposed == false)
               {
                 // Create a thread and start it
                 ThreadStart tsThread = new ThreadStart(IOCPFunction);
                 Thread thThread = new Thread(tsThread);
                 thThread.Name = "IOCP " + thThread.GetHashCode();
                 thThread.Start();
                 // Increment the thread pool count
                 IncCurThreadsInPool();
               }
             }
           }
         }
         catch
         {
         }
         // Relase the lock
         Monitor.Exit(GetCriticalSection);
        // Increment the number of active threads
         DecActThreadsInPool();
       }
     }
     catch
     {
     }
     // Decrement the thread pool count
     DecCurThreadsInPool();
   }

The last two public methods are the PostEvent methods.  The first method takes an integer as an argument and the second version takes no argument at all.  The integer is the data the application developer wishes to pass with the work posted into the IOCP thread pool.  In the PostQueuedCompletionStatus Win32 API call, we can see that the third argument is where we pass the data value.   Since this value is always an integer we set the size of the data to four, as seen in the second argument.  Like in the IOCPFunction, we check to see if we need to add a new thread to the pool.

   // Public Methods
   //******************************************
   /// <summary> IOCP Worker Function that calls the specified user function </summary>
   /// <param name="iValue"> SimType: A value to be passed with the event </param>
   /// <exception cref = "Exception"> Unhandled Exception </exception>
   public void PostEvent(Int32 iValue)
   {
     try
     {
       // Only add work if we are not disposing
       if (IsDisposed == false)
       {
         unsafe
         {
           // Post an event into the IOCP Thread Pool
           PostQueuedCompletionStatus(GetHandle, 4, (UInt32*) iValue, null);
         }
         // Increment the number of item of work
         IncCurWorkInPool();
         // Get a lock
         Monitor.Enter(GetCriticalSection);
         try
         {
           // If we have less than max threads currently in the pool
           if (GetCurThreadsInPool < GetMaxThreadsInPool)
           {
             // Should we add a new thread to the pool
             if (GetActThreadsInPool == GetCurThreadsInPool)
             {
               if (IsDisposed == false)
               {
                 // Create a thread and start it
                 ThreadStart tsThread = new ThreadStart(IOCPFunction);
                 Thread thThread = new Thread(tsThread);
                 thThread.Name = "IOCP " + thThread.GetHashCode();
                 thThread.Start();
                 // Increment the thread pool count
                 IncCurThreadsInPool();
               }
             }
           }
         }
         catch
         {
         }
         // Release the lock
         Monitor.Exit(GetCriticalSection);
       }
     }
     catch (Exception e)
     {
       throw e;
     }
     catch
     {
       throw new Exception("Unhandled Exception");
     }
   } 
   //*****************************************
   /// <summary> IOCP Worker Function that calls the specified user function </summary>
   /// <exception cref = "Exception"> Unhandled Exception </exception>
   public void PostEvent()
   {
     try
     {
       // Only add work if we are not disposing
       if (IsDisposed == false)
       {
         unsafe
         {
           // Post an event into the IOCP Thread Pool
           PostQueuedCompletionStatus(GetHandle, 0, null, null);
         }
         // Increment the number of item of work
         IncCurWorkInPool();
         // Get a lock
         Monitor.Enter(GetCriticalSection);
         try
         {
           // If we have less than max threads currently in the pool
           if (GetCurThreadsInPool < GetMaxThreadsInPool)
           {
             // Should we add a new thread to the pool
             if (GetActThreadsInPool == GetCurThreadsInPool)
             {
               if (IsDisposed == false)
               {
                 // Create a thread and start it
                 ThreadStart tsThread = new ThreadStart(IOCPFunction);
                 Thread thThread = new Thread(tsThread);
                 thThread.Name = "IOCP " + thThread.GetHashCode();
                 thThread.Start();
                 // Increment the thread pool count
                 IncCurThreadsInPool();
               }
             }
           }
         }
         catch
         {
         }
         // Release the lock
         Monitor.Exit(GetCriticalSection);
       }
     }
     catch (Exception e)
     {
       throw e;
     }
     catch
     {
       throw new Exception("Unhandled Exception");
     }
   }
  }
}

We have now completed the implementation of the IOCP thread pool class.  Now it is time to test it.

IOCP Thread Pooling in C# - Part II - The Sample Application
(Page 2 of 3 )

Start by adding a new class to your C# project.  Remove all of the code provided by the Visual Studio .NET wizard.  Then add all of the following code.  In Main, an IOCP thread pool is created, and a single piece of work is posted to the IOCP thread pool.  We pass the data value of 10 along with the posted work. 

The main thread is then put to sleep. This gives the IOCP thread function time to wake up to process the work posted.  The last thing in main is to dispose the IOCP thread pool.  The IOCP thread function displays the value of the data passed into the IOCP thread pool.

using System;
using System.Threading;  // Included for the Thread.Sleep call
using Continuum.Threading;
namespace Sample
{
  //============================================
  /// <summary> Sample class for the threading class </summary>
  public class UtilThreadingSample
  {
   //*******************************************
   /// <summary> Test Method </summary>
   static void Main()
   {
     // Create the MSSQL IOCP Thread Pool
     IOCPThreadPool pThreadPool = new IOCPThreadPool(0, 5, 10, new IOCPThreadPool.USER_FUNCTION(IOCPThreadFunction));
     pThreadPool.PostEvent(10);
     Thread.Sleep(100);
     pThreadPool.Dispose();
   }
   //*****************************************
   /// <summary> Function to be called by the IOCP thread pool.  Called when
   ///           a command is posted for processing by the SocketManager </summary>
   /// <param name="iValue"> The value provided by the thread posting the event </param>
   static public void IOCPThreadFunction(Int32 iValue)
   {
     try
     {
       Console.WriteLine("Value: {0}", iValue);
     }
     catch (Exception pException)
     {
       Console.WriteLine(pException.Message);
     }
   }
  }
}

This is what you should see when you run the sample application.  On your own change the main function to call the PostEvent method several times and see how the IOCP thread pool performs.


"Network Programming" 카테고리의 다른 글
  • Socket Programming in C# (0)2007/07/27
  • Network Programming in C# (0)2007/07/27
  • IOCP Thread Pooling in C# (0)2007/07/26
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
2007/07/26 14:07 2007/07/26 14:07
Posted by webdizen
Tags C#, IOCP, Pooling, Thread
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/3095

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/14 17:13

UDP 프로그래밍의 기초

이번 문서는 UDP 프로그래밍에 관한 내용입니다. 최소한의 UDP 프로그래밍을 할수 있는 수준의 간단한 문서입니다. 많은 도움이 되었으면 좋겠군요.



1절. 소개
2절. UDP 프로그래밍
2.1절. UDP란
2.1.1절. connectionless
2.1.2절. unreliable
2.1.3절. 프로그래머 관점에서 봤을때의 특징
2.1.4절. UDP 를 어디에 사용할수 있을까?
2.1.5절. UDP 를 이용하는 서버 작성
2.1.6절. UDP 를 이용하는 클라이언트 작성
2.2절. 예제 프로그램
2.2.1절. 서버 에제
2.2.2절. 클라이언트 예제
2.2.3절. 문제점

--------------------------------------------------------------------------------

1절. 소개
우리는 그동안 소켓 프로그래밍을 하면서 TCP/IP 를 이용한 프로그래밍을 했었다. 이번에는 TCP와 같은 레벨의 또다른 프로토콜인 UDP 프로그래밍에 대해서 알아보도록 할것이다.


--------------------------------------------------------------------------------

2절. UDP 프로그래밍
2.1절. UDP란
TCP/IP 4계층에서 봤을때 UDP 는 TCP 와 같은 Transport Layer 에 위치한다. 즉 UDP와 TCP는 동급의 프로토콜로 데이타를 전송하기 위해서 사용되는 프로토콜이다.

TCP가 연결지향적이고 신뢰할수 있는 데이타의 흐름을 제공하는 반면 UDP는 비연결지향성(connectionless)이며, 데이타의 흐름을 신뢰할수 없다는 특징이 있다.


--------------------------------------------------------------------------------

2.1.1절. connectionless
TCP는 서로 통신을 하기전에 상대방을 확인하는 절차를 가짐으로써, session(통신선로)를 맺는 작업을 하며, 연결된 session 을 통해서 데이타의 흐름이 이루어진다. 그러나 UDP 는 이러한 session 을 만들기 위한 작업을 하지 않고, 그냥 보내고 받기만을 한다. 그러므로 우리가 UDP 서비스를 하는 서버로 메시지를 보냈다고 하더라도, 메시지가 실제로 도착되었는지는 알수가 없다. 데이타는 보내질수도 있고 그렇지 않을수도 있다.


--------------------------------------------------------------------------------

2.1.2절. unreliable
또한 TCP와 달리 신뢰할수가 없다. TCP는 프로토콜자체에 메시지가 제대로 보내졌음을 체크할수 있는 다양한 장치를 가지고 있다. 즉 각 패킷에 순서를 매겨서, 순서가 뒤엉키지 않도록 재조립하며, 일정시간 동안 패킷이 도착하지 않으면, 해당 패킷을 다시 보내달라고 요청할수도 있다. 그러나 UDP는 이러한 어떠한 장치를 가지고 있지 않는다. UDP 로 전송된 패킷은 순서가 뒤바뀔수도 있으며, 중간에 패킷이 손실될수도 있다. 프로토콜 차원에서 패킷의 순서가 뒤바뀌었는지, 패킷이 손실되었는지 알수 있는 방법은 없다.

UDP 패킷에 신뢰성을 주기 위해서는 application 차원에서 직접 코딩을 해주어야만 한다. 보통은 패킷을 만들때 데이타 헤더를 따로 만들어서 일련번호등을 넣어서 서버측에 보내고 서버측에서는 이에 대한 응답을 보내는 방식을 이용하여 UDP 패킷에 신뢰성을 부여한다.

이렇듯 UDP 는 단순히 데이타 그램 위주의 통신을 하기 때문에, 데이타 그램 지향 프로토콜 이라고 불리우기도 한다. 실제로 UDP는 User Datagram Protocol 의 줄임말이다.


--------------------------------------------------------------------------------

2.1.3절. 프로그래머 관점에서 봤을때의 특징
UDP는 TCP 프로토콜이 가지고 있는 다양한 기능을 가지고 있지 않다. 당연히 더 간단하고, 더빠른 처리를 보장해준다. 또한 프로그래밍 하기도 더욱 간단하다. 나중에 예제를 들겠지만 UDP 를 이용하는 서버의 경우 listen, accept 를 할필요 없이 그냥 소켓을 생성하고, 읽을 데이타가 있는지 기다리기만 하면된다(connectionless 이므로 당연히 클라이언트의 accept 를 기다릴 필요가 없다).


--------------------------------------------------------------------------------

2.1.4절. UDP 를 어디에 사용할수 있을까?
언뜻 생각하기에 UDP는 TCP에 비해서 사용하기에 문제가 있을거라고 생각할수 있다. 그러나 UDP는 그 나름대로 적당한 사용처가 있다.

첫번째가 음성및 비디오를 위한 실시간 스트리밍 서비스이다. 음성서비스를 TCP로 해버릴경우의 문제점은 패킷이 중간에 빠질경우 음성비스가 중단되어버린다는 점이다(빠진 패킷에 대한 재 전송을 요청하므로). 하지만 이건 바람직한 현상이 아니다. 이건 마치 우리가 전화를 할때 중간에 약간의 잡음이 생겼다고 해서, 전화가 중단되는 것과 마찬가지의 상황이다. 우리는 약간의 잡음 때문에 (혹은 한두자 정도 언어가 전달이 안되는) 그걸 교정하느라고 서비스가 중단되는 것 보다는 서비스질이 약간 떨어지더라도 계속적으로 서비스가 되는걸 원할것이다. 즉 통신품질보다는 통신의 연속성이 더욱 중요시 되는곳에 유용하게 사용될수 있다. (물론 TCP로도 구현할수 있으며, 상당수의 서비스가 TCP로 서비스 된다. 다만 이러한 특징을 가지고 있음을 설명하는 것이다.)

두번째는 상당히 많은 패킷이 오가면서, 별로 중요하지 않은 몇개의 데이타 손실 정도는 눈감아줄수 있는 곳이다. 가장 유명한게 start craft 의 베틀넷 서비스가 아닐가 싶다. 이 베틀넷 서비스에는 수많은 유저가 접속해서 사용할건데, 서비스의 모든 부분에 TCP를 사용하기에는 TCP는 너무 느린 감이 있다. 특히 게임을 할때 서로 교환되는 수많은 패킷의 경우 매우 중요한 데이타가 아니므로, 그리고 게임의 흐름이 끊기면 안되므로 UDP로 처리되는게 더 유리할것이다.

이밖에도 UDP 를 통신 프로토콜로 사용하는 서비스로는 DNS 와 NFS, SNMP, syslog 등이 있다.


--------------------------------------------------------------------------------

2.1.5절. UDP 를 이용하는 서버 작성
UDP 서버역시 socket 를 이용해서 통신을 하지만 TCP와는 달리 연결지향이 아니므로 listen 과정과 accetp 과정이 필요 없다. socket > bind 과정후 만들어진 소켓 지정 번호 에서 데이타가 있을경우 이를 읽기만 하면 된다. 또한 클라이언트와 연결을 맺지 않기 때문에, 각 클라이언트의 요청해결을 위해서 fork, select, poll, thread 등을 이용해서 프로세스를 분기할 필요가 없다. 기본적으로 UDP를 이용한 서버의 경우 최초 socket 함수를 이용해서 만들어진 소켓 지정 번호 만을 가지고 통신이 가능하다. TCP에 있어서 최초 만들어진 소켓 지정 번호가 클라이언트의 연결을 accept 하기 위한 end point 전용으로 쓰이는 것과는 다르다. TCP를 이용한 플로그래밍에 있어서는 각 클라이언트와의 연결을 위해서 최초 생성된 하나의 소켓 지정 번호를 end point 로 하고 연결이 만들어지면 전용 통신 선로를 위한 소켓 지정 번호를 생성하고 이 소켓 지정 번호를 이용해서 통신을 하게 된다. UDP 는 이러한 작업이 필요 없으므로 서버를 매우 간편하고, 직관적으로 이해하기 쉽게 만들수 있다(단지 while 문을 돌리기만 하면 된다).

여기에서 한가지 의문점이 생긴다. 연결을 맺지 않는다고 했는데, 그렇다면 어떻게 여러개의 클라이언트로 부터 요청을 받았을때, 요청한 클라이언트에게 결과 데이타를 보낼수 있을까(단지 하나의 소켓 지정 번호를 이용해서)? 가장 간단하게 생각해볼수 있는 방법은 데이타를 받을때, 데이타를 보낸 클라이언트의 정보를 받아오고, 이 클라이언트의 정보를 토대로 데이타를 보내면 될것이다. Unix 는 이러한 함수를 제공하고 있다.

int recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);

recvfrom 과 sendto 를 이용해서 원하는 클라이언트로 데이타를 보낼수 있다. recvfrom 은 TCP와 UDP 모두에서 사용이 가능한데, UDP 에서 사용할경우 sockaddr 구조체가 채워져서 돌아온다. 그러므로 우리는 클라이언트의 연결 정보를 알수 있게 된다. INET 서버의 경우라면 struct sockaddr_in 을 사용하게 될것이다. 우리는 sockaddr_in 의 멤버 변수를 확인함으로써 port 와 address 등 통신을 위해서 꼭 필요한 정보를 얻을수 있다.

sockaddr_in 은 /usr/include/netinet/in.h 에 선언되어 있다.

sendto 도 recvfrom 과 마찬가지로 TCP/UDP 모두에 사용가능하며, struct sockaddr 에 메시지를 보낼 호스트(클라이언트 혹은 서버 호스트)의 정보를 채워 넣음으로써 원하는 클라이언트 에게 메시지를 보낼수 있다.


--------------------------------------------------------------------------------

2.1.6절. UDP 를 이용하는 클라이언트 작성
UDP 클라이언트는 그야말로 초 간단이다. socket 을 열고나서 sendto 함수이용해서 쓰기만 하면 그걸로 끝이다.


--------------------------------------------------------------------------------

2.2절. 예제 프로그램
이제 간단한 예제를 만들어 보도록 하겠다. 덧셈 서버/클라이언트로, 클라이언트측에서 2개의 숫자를 보내면 서버측에서 이걸 받아서 더한다음 돌려주는 간단하지만 UDP 의 프로그래밍을 하기 위한 최소한의 내용을 담고 있다.


--------------------------------------------------------------------------------

2.2.1절. 서버 에제
예제 : sum_server.c


예제 프로그램은 더할나위 없이 간단하다. 소켓을 생성해서 bind 하는것 까지는 TCP 프로그래밍과 매우 비슷하다. 다른것이 있다면 최초 socket 함수를 호출할때 2번째 인자로 SOCK_STREAM 대신 SOCK_DGRAM 을 쓴다는것이다. SOCK_STREAM 을 명시해 줌으로써 UDP 소켓을 사용할수 있다. 그리고 listen, accept 함수가 없이 바로 데이타 전송/수신 과 관련된 함수를 호출함을 알수 있다. 이는 클라이언트와 연결을 생성시키지 않기 때문이다.

그리고 redvfrom 함수를 호출하여서, 클라이언트로 부터 데이타를 읽어 들이고 읽어들인 데이타를 더하고 그 결과값을 sendto 를 이용해서 클라이언트측으로 보낸다. recvfrom과 sendto 의 5번째 아규먼트를 주목하기 바란다. 5번째 아규먼트로 클라이언트의 소켓구조체 주소를 가져옴으로 우리는 다중의 클라이언트에 대한 요청을 처리할수 있게 된다.


--------------------------------------------------------------------------------

2.2.2절. 클라이언트 예제
예제 : sum_client.c


클라이언트는 더 간단하다. socket 만 만들고 나서 바로 통신에 들어간다. 클라이언트는 아규먼트로 2개의 숫자를 받아들인 다음 이것을 서버에 보내고, 서버의 결과값(더한값)을 가져오고, 이것을 출력시켜준다.


--------------------------------------------------------------------------------

2.2.3절. 문제점
위의 UDP 를 이용한 서버/클라이언트 모델은 몇가지 문제점을 가지고 있다. 위의 예제를 가지고 테스트를 해보면 알겠지만, 서버 프로그램이 떠있지 않더라도 클라이언트는 이를 감지 하지 못하고, 메시지를 보낸다. 또한 메시지가 정확히 전달되었는지 그렇지 않은지 클라이언트는 감지 하지 못한다. 데이타를 보내는 걸로 끝이기 때문이다. 그리고 무작정 서버로부터의 응답을 기다리는데, 서버는 죽어 있음으로 당연히 클라이언트는 응답을 받지 못할것이고, 클라이언트는 계속 block 된 상태로 떠있게 될것이다

사실 이건 어쩔수 없는 문제이다. UDP 프로토콜 자체가 데이타의 흐름을 제어할수 있는 어떤 장치를 제공해주지 않기 때문이다. 이를 해결하기 위해서는 어플리케이션 차원에서 해결하는 수 밖에 없다. 즉 최초에 서버에 어떤 메시지를 보내고(HELO 메시지) 일정시간안에 서버로 부터 메시지가 도착하는지 확인하고나서, 통신을 시작하는 것이다. 통신할때도 역시 일정시간안에 응답 메시지가 서버로 부터 도착하는지를 확인해주어야 할것이다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • Network Programming in C# (0)2007/07/27
  • IOCP Thread Pooling in C# (0)2007/07/26
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
  • libpcap 프로그래밍 (0)2007/05/14
2007/05/14 17:13 2007/05/14 17:13
Posted by webdizen
Tags Network, UDP, UDP 프로그래밍
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2944

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/14 17:02

ICMP 프로그래밍

ICMP 는 인터넷 상에서 호스트에 네트웍단절과 같은 문제를 확인하기위한 방법으로 많이 사용한다. 이번문서는 ICMP를 이용한 프로그래밍에 대한 내용과 또한 ICMP 프로토콜에 대한 개괄적인 내용또한 담고 있다.

1절. 소개
2절. ICMP 프로토콜에 대해서
2.1절. ICMP 의 사용목적
2.2절. ICMP 프로토콜의 구조
3절. ICMP 프로그래밍

--------------------------------------------------------------------------------

1절. 소개
이문서는 실제로 ICMP 를 어떻게 이용할수 있는지에 대한 내용을 담고 있다. 간단한 ICMP 프로토콜에 대한 개요를 설명한후에 socket 를 이용해서 어떻게 ICMP 프로토콜의 사용이 가능한지에 대해서 얘기하게 될것이다.

이 문서는 여러분이 네트웍 프로토콜들과 TCP/IP 4계층과 socket 프로그래밍 환경에 대한 기본적인 이해를 하고 있다는 가정하에 만들어 졌다. 이들 내용은 이 사이트에서 여러개의 문서에 걸쳐서 다루고 있다. 네트웍 프로그래밍 섹션과 TCP/IP 섹션의 문서들을 참고하기 바란다.


--------------------------------------------------------------------------------

2절. ICMP 프로토콜에 대해서
2.1절. ICMP 의 사용목적
ICMP 는 Inernet Control Message Protocol 의 줄임말이다. ICMP 프로토콜은 보통 다른 호스트나 게이트웨이 와 연결된 네트웍에 문제가 있는지 확인하기 위한 목적으로 주로 사용된다.

ICMP 를 이용한 가장 유명한 프로그램으로는 ping 프로그램이 있다. 우리는 ping 프로그램을 애용해서 특정한 게이트웨이, 호스트, 라우터 등이 제대로 작동을 하고 있는지 등을 조사하며, ICMP 요청에 대한 응답시간을 검사 함으로써 네트웍 상태도 어느정도 확인할수 있다.


--------------------------------------------------------------------------------

2.2절. ICMP 프로토콜의 구조
ICMP 메시지는 기본적으로 IP header 를 이용해서 보내어진다. IP header 정보를 보면 (IP 자세히보기 를 참조하라), 9번째 필드가 protocol 을 위해서 사용되고 있음을 알수 있을것이다. ICMP protocol 을 위해서는 "1" 을 사용한다.

ICMP 는 다음과 같은 구조를 가진다. 첫번째 32 비트까지가 ICMP 헤더이며, 나머지부분은 ICMP 데이타이다. 이 데이타 영역은 ICMP의 어떤 기능을 이용할것이냐에 따라 다르게 설정될수 있다.
0                                           31
+------------+-------------+-----------------+
| Type       | Code        | CheckSum        |
+------------+-------------+-----------------+
| 가변 데이타                                |
|                                            |
                       

Type 필드에는 ICMP 오류 메시지의 종류를 식별하는 코드가 채워진다. Code 는 각 Type 종류에 대한 자세한 오류의 유형을 알려주기 위해서 사용된다. 이 Type 에는 다음과 같은 종류가 있다.

표 1. ICMP Type 필드 유형

0 icmp echo replay icmp 요청에 대한 icmp 응답
3 Destination Unreachable Message 수신지까지 메시지가 도착할수 없음
4 Source Quench Message 송신지 억제
5 Redirect Message 재지시
8 icmp echo request 목적지 호스트에 ICMP 응답을 요청한다
11 Time Exceeded Message 데이타그램 시간초과(TTL 초과)
12 Parameter Problem Message 데이타그램에서의 파라메타 문제
13,14 Timestamp or Timestamp Reply Message 13:시간기록요청, 14:시간기록응답


Code 필드는 위에서 말했듯이 Type 에 따라 각각 다른 값을 가진다. 예를들어서 Type 3 번에 Code 0 번이 발생했을경우에는 오류 메시지 종류는 "수신지까지 메시지가 도착할수 없음" 이며, 그 이유는 Redirect datagrams for the Network, 즉 "네트웍을 획술할수 없음"이 된다. 이문서에서는 각 ICMP Type 에 따른 Code 까지 설명하진 않겠다. 이에 대한 자세한 내용은 rfc729 를 참고하기 바란다.


--------------------------------------------------------------------------------

3절. ICMP 프로그래밍
그럼 이제 ICMP 를 이용해서 실제 프로그래밍을 해보도록 하겠다.

ICMP 응답을 위해서 전송해야할 ICMP 헤더정보는 다음과 같다(rfc 문서에 정의되어 있음).     0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |     Type      |     Code      |          Checksum             |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |           Identifier          |        Sequence Number        |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |     Data ...
  +-+-+-+-+-
               

그러므로 우리는 Type, Code, Checksum, Identfier, Sequence Number 를 채워서 완전한 ICMP 패킷을 만들어줘야 한다. Type 는 8번이 될것이고, Code 는 0, Checksum, Identifier, Sequence Number 은 적당히 만들어줘서 채워주면 될것이다. 될것이다. Identifier 와 Sequence Number 는 내가 보낸 ICMP 응답에 대한 메시지인지를 확인하기 위한 목적으로 사용한다. 만약 내가 Identifier 를 120 으로 세팅해서 보냈다면, 수신된 ICMP 메시지의 Identifer 이 120인지를 확인함으로써, 내가 보낸 ICMP 요청에 대한 응답인지를 확인가능하다. 여기에 Sequence Number 를 이용함으로써, 패킷의 일련번호까지 확인할수 있다.

그럼 실제 프로그래밍 과정을 통해서 확인해 보도록 하자. 일단 socket 를 만들때 RAW 소켓(생소켓 혹은 날소켓이라고도 한다). ICMP 는 IP 와 같은 계층에 있음으로 TCP/UDP 소켓을 이용한 접근은 불가능하기 때문이다. 어쩔수 없이 RAW 소켓을 이용해서 직접 ICMP 헤더를 고쳐주어야 한다.

icmp 헤더를 세팅하는건 icmp 구조체에 필요한 값을 써줌으로써 간단히 해결할수 있다. icmp 헤더구조체는 /usr/include/netinet/ip_icmp.h 에 선언되어 있다. 구조체를 보면 상당히 많은 다양한 멤버 변수들을 가지고 있는데, 우리는 이들 값중 Type, Code, Checksum, Identifier, Sequence Number 만을 사용하면된다. 이들을 가르키는 멤버변수는 각각 icmp_type, icmp_code, icmp_id, icmp_seq 이다.

예제 : icmp_echo.c         


in_cksum 함수는 다른 ICMP 를 이용하는 프로그램에서 공통적으로 사용된다. checksum 을 만들기 위한 알고리즘 정도로 생각하면 될것 같다. 실제로 ping, aping, fping 등의 관련 어플리케이션에서 사용되어지고 있다.

이제 위의 코드를 컴파일한후 테스트를 해보자.
[root@local ping]# ./icmp_echo 66.218.71.89
reply from 66.218.71.89
Type : 0
Code : 0
Seq  : 15
Iden : 2323
               

ICMP ECHO REPLY(Type 0) 로 ICMP ECHO(Type 8) 에 대한 응답이 왔음을 알수 있다. 또한 Seq과 Iden값을 이용해서 icmp_echo 가 보낸 어플리케이션의 ICMP ECHO 에 대한 응답임을 알수 있다.

심심한데? tcpdump 를 이용해서 실제 패킷이 어떻게 이동하는지 알아보자. 아래는 위의 결과를 tcpdump 한 화면이다. -x 는 16진수 코드로 출력받기 원할때 사용하는 옵션이다.
[root@coco ping]# tcpdump icmp -x
11:08:00.763994 eth0 > localhost > w10.scd.yahoo.com: icmp: echo request (DF)
                        4500 0030 0000 4000 4001 8b6f c0a8 6482
                        42da 4759 0800 d5f6 1309 0f00 0000 0000
                        0000 0000 0000 0000 0000 0000 0000 0000
11:08:00.933994 eth0 < w10.scd.yahoo.com > localhost: icmp: echo reply (DF)
                        4500 0030 8352 4000 3501 131d 42da 4759
                        c0a8 6482 0000 ddf6 1309 0f00 0000 0000
                        0000 0000 0000 0000 0000 0000 0000 0000
...
               

위의 dump 화면에서 하나의 문자는 4비트를 나타낸다. 위의 dump 를 간단히 분석해 보자면 4500 ~ 4759 까지가 TCP 헤더이고, 나머지 부분이 ICMP 헤더+데이타 부분임을 알수 있다(IP표준 헤더의 크기는 160 bit 임으로). ICMP 헤더부분은 0800 에서 d5f6 까지의 부분이며(ICMP 표준 헤더크기는 32 bit 임으로), 1309 이하가 ICMP 데이타 부분임을 알수 있다.

또한 우리는 IP 의 버전이 4 이고 프로토콜이 1을 사용하고 있음을 알수 있다. IP 헤더의 처음 4비트가 Version 정보를 나타내므로 4500 의 4가 version 정보, 5가 IHL정보 임을 알수 있다. 이런식으로 찾아보면 protocol 정보가 72bit 후에 존재하고 8bit 크기를 가짐으로 dump 의 5번째 값인 4001 의 01 임을 알수 있다. 그러므로 40 은 TTL 임을 알수 있을것이다. 또한 source address 는 c0a8 6482 destination address 는 42da 4759 임을 유추해 낼수 있을것이다(IPv4 의 주소체계에서 주소는 32비트 크기를 가짐으로). IP헤더 정보는 IP 자세히보기 를 참조하기 바란다.

그럼 ICMP 를 분석해보도록 하자. Type와 Code 는 각각 8bit 크기를 가짐으로 0800 이 Type 와 Code 를 가리킴을 알수있다. d5f6 는 cheksum 이며 1309 가 바로 Identifier 이다. 1309 가 정말로 우리가 입력한 Identifier 번호인 2323 인지 확인해보길 원한다면 10 진수를 16진수로 변환 가능한 계산기를 이용해서 계산해 보면된다. 0f 는 우리가 입력한 Sequence Number 15 임을 알수 있다.

w10.scd.yahoo.com 에서 넘어온 ICMP ECHO REPLAY dump 화면의 Identifier 과 Sequence Number 가 일치함을 알수 있다. 그러므로 우리는 해당 ICMP ECHO REPLAY 패킷이 우리가 전송한 ICMP ECHO에 대한 응답 패킷임을 알수 있다.

위 프로그램은 ICMP ECHO 체크를 위한 최소한의 기능만을 가지고 있다. 만약에 ICMP REPLAY 가 되지 않는 IP에 대해서는 계속 블럭된 상태로 있게 될것이다. 이럴때는 기다리는 시간을 체크하는 방법등을 이용해서 체크를 해주어야 할것이다.

솔라리스도 위의 코드 수정없이 사용하능하지마,
추가시켜줘야할 헤더파일들이 몇개 있습니다.



그리고 ICMP 메시지는 기본적으로 방송(브로드캐스팅) 됩니다.
간단한 테스트를 위해서 위의 icmp_echo 를 단지 응답만을 기다리도록
수정한뒤 한 2개 정도 띄워놓습니다.


그다음 ping 프로그램을 이용해서 아무 서버에나 ping 을 보내고 응답을
기다리면 ping 에도 응답이 가고 icmp_echo 2개 에도 모두 응답이 가는걸
확인하실수 있을겁니다.



RAW 소켓을 다루기 위해서는 root 권한이 필요합니다.
ping 프로그램을 보더라도 SID가 주어져 있음을 알수 있습니다 .
위 프로그램을 일반유저로 실행시킬려면 sid 권한이 주어져야 합니다.
chmod +s 를 이용하면 됩니다.


이러한 특징 때문에 icmp 패킷에 chksum 과 일련번호를 두어서 ICMP 응답을
구분할수 있도록 하고 있습니다.



출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • IOCP Thread Pooling in C# (0)2007/07/26
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
  • libpcap 프로그래밍 (0)2007/05/14
  • pcap 을 이용한 id,password 정보가져오기 (0)2007/05/14
2007/05/14 17:02 2007/05/14 17:02
Posted by webdizen
Tags ICMP, Network
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2942

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/14 10:24

libpcap 프로그래밍

libpcap 은 인터넷상의 패킷을 캡쳐하기 위한 라이브러리로, 패킷분석을 통한 침입탐지 시스템, 네트웍 관리 프로그램, 네트웍 문제분석을 위한 도구제작등에 널리 사용되고 있다. 이 문서는 libpcap 을 사용한 기본적인 패킷캡쳐 방법을 담고 있다.

1절. 소개
2절. Libpcap 기본
2.1절. Libpcap 에 대하여
2.2절. libpcap 의 설치
2.3절. 패킷 캡쳐의 기본이해
2.4절. 패킷 캡쳐의 응용
3절. libpcap 프로그래밍
3.1절. 디바이스&네트웍 정보 관련 API
3.1.1절. int pcap_lookupnet()
3.1.2절. char* pcap_lookupdev
3.1.3절. pcap_datalink
3.1.4절. 예제
3.2절. 패킷 캡쳐 초기화 관련 API
3.2.1절. pcatp_t *pcap_open_live
3.2.2절. pcap_t *pcap_open_offline
3.3절. 패킷 캡쳐(Read) 관련 API
3.3.1절. TCP,IP,Eternet 구조체
3.3.2절. u_char *pcap_next
3.3.3절. pcap_loop
3.3.4절. pcap_dispatch
3.4절. 패킷 필터링 관련 API
3.4.1절. pcap_compile
3.4.2절. pcap_setfilter
4절. 예제 코드
5절. 결론

--------------------------------------------------------------------------------

1절. 소개
이번 강좌는 libpcap 을 사용한 패킷 캡춰에 대한 내용이다.


--------------------------------------------------------------------------------

2절. Libpcap 기본
2.1절. Libpcap 에 대하여
Libpcap(이하 pcap)은 "Portable Packet Capturing Library"의 줄임말이며, 해석그대로 "간단하게 패킷을 캡쳐하기 위한 함수모음(라이브러리)" 이다.

물론 pcap 외에도 패킷캡쳐를 위한 도구들이 있기는 하지만, 대부분의 경우 운영체제에 종속적이여서, 운영체제별로 코드를 다시 짜야 한다는 불편함이 있다. 대표적인 도구로는 SOCK_PACKET, LSF, SNOOP, SNIT 등이 있다.

이에 비해 pcap 는 운영체제에 상관없이 범용적으로 사용가능한 API를 제공해줌으로, 공용프로그램 혹은 공용라이브러리의 제작이 가능하도록 도와준다. 또한 간단하게 사용가능한 사용자 레벨 라이브러리이다.

libpcap 를 이용한 가장 대표적인 프로그램이 tcpdump 와 SAINT 와 같은 프로그램들이다.

또한 상용 IDS [1] 제품의 상당수가 패킷분석을 위해서 libpcap 을 사용하고 있다.


--------------------------------------------------------------------------------

2.2절. libpcap 의 설치
여러분이 Unix 계열 운영체제를 사용하고 있다면, 거의 대부분 tcpdump 를 곧바로 사용할수 있을것이다. tcpdump 를 사용할수 있다는 것은 그 기반이 되는 libpcap 역시 설치되어 있다는 말이 된다.

그러나 만약의 경우 설치가 되어 있지 않다면 tcpdump.org 에서 받아서 컴파일후 설치하기 바란다.

컴파일 하기가 귀찮다면 그리고 레드헷이나 데비안 계열의 리눅스 사용자라면 해당 패키지를 배포하는 ftp 사이트에서 다운 받아서 설치하면 된다. 솔라리스 운영체제 라면 www.sunfreeware.com 에서 패키지를 받아서 설치하기 바란다.


--------------------------------------------------------------------------------

2.3절. 패킷 캡쳐의 기본이해
패킷 캡쳐는 네트웍 상에서 돌아다니는 패킷을 들여다 보는 걸 말한다. 패킷 캡쳐라는 어감상 패킷을 "잡는"게 아닌가 라고 생각할수 있지만, 패킷을 "잡지"는 않고 단지 들여다만 볼 뿐이다.

만약 여러분의 호스트가 포함된 네트웍을 관리하는 라우터가 일반적인(스위칭이 아닌) 라우터라면, 내부로 향하는 모든 패킷은 브로드캐스팅(Broadcasting) 된다. 이는 스위칭 라우터가 아닌한은 모든 로컬네트웍의 패킷을 들여다 볼수 있음을 의미하기도 한다. 어쨋든 이경우 운영체제는 자신에게 도착된 패킷중 목적지가 자신인 패킷만을 처리해서 Application Layer 까지 올려 보내게 된다.

libpcap 을 사용하면 이러한 패킷의 캡쳐가 가능해진다. 인터넷 상의 패킷은 상대방에게 보낼경우 encapuslation 과정을 거치고, 받은 패킷에 대해서는 demultiplexing 과정을 거친다는 것을 알고 있을것이다 - TCP/IP 개요(3) 참고 - libpcap 을 사용해서 캡쳐한 패킷은 demultiplexing 과정을 거치기 전의 패킷이다. 이렇게 해서 캡쳐한 패킷은 각 프로토콜 단위로(구조체) 읽어서 처리하면 된다. 다음은 encapuslation&demultiplexing 과정이다.

사용자 삽입 이미지
그림 1. Encapuslation & demultiplexing



2.4절. 패킷 캡쳐의 응용
패킷 캡쳐는 여러가지 목적으로 사용될수 있다. NIDS(Network Intrusion Detection System) 프로그램이 가장 대표적인 응용이며, 네트웍 트래픽 감시, 네트웍 디버깅을 위한 용도로 사용가능하다.


--------------------------------------------------------------------------------

3절. libpcap 프로그래밍
이번장에서는 libpcap 에서 필수적으로 사용되는 중요 API 에 대해서 알아볼것이다.


--------------------------------------------------------------------------------

3.1절. 디바이스&네트웍 정보 관련 API
3.1.1절. int pcap_lookupnet()

                               



네트웍 디바이스에 대한 네트웍 및 mask 번호를 되돌려준다. 네트웍 번호는 netp에 mask 번호는 maskp에 저장된다. device는 pcap_lookupdev 등을 통해 얻어온 네트웍 디바이스 이름이다.

에러가 발생할경우 -1 이 리턴되며, 에러 내용이 errbuf 에 저장된다.


--------------------------------------------------------------------------------

3.1.2절. char* pcap_lookupdev
pcap_open_live() 와 pcap_lookupnet() 에서 사용하기 위한 네트웍 디바이스에 대한 포인터를 되돌려준다. 성공할 경우 "eth0", "eth1" 과 같은 이름을 되돌려주며 실패할경우 0을 되돌려준다.


--------------------------------------------------------------------------------

3.1.3절. pcap_datalink

                               

link layer 타입을 되돌려준다(DLT_EN10MB 과 같은)


--------------------------------------------------------------------------------

3.1.4절. 예제
예제 : pcap.c

                               

다음은 컴파일 방법이다. 컴파일 환경은 Redhat Linux 8.x, gcc 3.x 이다.
[root@localhost test]# gcc -o pcap pcap.c -lpcap -I/usr/include/pcap
                               

pcap.h 의 위치는 운영체제 마다 다를수 있으니 확인후 컴파일 하기 바란다.

다음은 실행결과이다.
[root@localhost test]# ./pcap
DEV: eth0
NET: 192.168.xxx.x
MASK: 255.255.xxx.x
                               




--------------------------------------------------------------------------------

3.2절. 패킷 캡쳐 초기화 관련 API
파일관련 작업을 할때 file descriptor(파일지정자)를 이용해서 작업하는것과 마찬가지로, 패킷 캡쳐관련 작업을 할때에도 packet capture descriptor 를 가지고 작업을 한다.

packet capture descriptor 는 pcatp_t * 형으로 선언되어 있다.


--------------------------------------------------------------------------------

3.2.1절. pcatp_t *pcap_open_live

                               

첫번째 인자로 주어지는 네트웍 디바이스 device에 대한 packet capture descriptor(이하 PCD) 을 만들기 위한 함수이다. 패킷을 캡춰하는 실질적인 모든일은 pcap_open_live 함수를 호출해서 만들어진 PCD 를 이용해서 이루어지게 된다.

linux 커널 2.2 이상의 경우 device 를 "any" 혹은 NULL로 할경우 모든 네트웍디바이스에 대해서 패킷 캡쳐가 일어나게 된다.

snaplen은 받아들일수 있는 패킷의 최대 크기(byte)이다.

promisc 는 네트웍 디바이스를 promiscuous mode 로 할것인지를 결정하기 위해서 사용한다. promisc 가 1일경우 promiscuous 모드가 되며, 로컬 네트웍의 모든 패킷을 캡쳐하게 된다. 0 일경우 에는 자기에게만 향하는 패킷을 캡쳐하게 되는데, 몇몇 경우에 있어서 promiscuous 모드로 작동하기도 한다.

to_ms 는 읽기 시간초과(time out) 지정을 위해서 사용되며 millisecond 단위이다.

ebuf 는 pcap_open_live 함수 호출에 문제가 생겼을경우 에러 메시지를 저장하기 위해서 사용한다. 만약 pcap_open_live 함수 호출시 에러가 발생할경우 NULL 을 리턴하고 에러내용을 ebuf 에 복사한다.


--------------------------------------------------------------------------------

3.2.2절. pcap_t *pcap_open_offline
       

                               

fname 를 가지는 파일로 부터 패킷을 읽어들인다. 만약 fname 이 "-" 일 경우 stdin으로 부터 읽어들인다.

ebuf 는 에러메시지를 저장하기 위해서 사용된다.


--------------------------------------------------------------------------------

3.3절. 패킷 캡쳐(Read) 관련 API
이번장에서는 실제 패킷을 캡쳐하는 관련 API 들에 대해서 알아볼것이다. 이 패킷 캡처 관련 API 를 제대로 이해하고 사용하기 위해서는 TCP/IP와 이더넷 프로토콜의 구조에 대해서 어느정도 이해를 해야 한다. 그럼으로 API 를 다루기 전에 이들 대표적인 프로토콜들에 대한 헤더 정보에 대해서 간략하게 먼저 알아보도록 하겠다.


--------------------------------------------------------------------------------

3.3.1절. TCP,IP,Eternet 구조체
패킷 Read 관련 API에서는 패킷을 읽었을때, Demultiplexing 이 되지 않은 완전한 구조의 패킷을 넘겨준다. 그럼으로 최소한 이들 각 패킷의 구조체 정보를 알고 있어야 각 계층(Layer)의 데이타를 읽어올수 있다.

다음은 TCP, IP, Eternet 구조체정보이다.





ip, tcp 헤더 파일은 /usr/include/netinet 밑에서 찾을수 있으며, ethernet 헤더 파일은 /usr/include/linux/if_ether.h 에서 찾을수 있다.

Ethernet 헤더와 IP 헤더의 경우 demultiplexing 과정을 거치기 위해서 상위 Layer 의 프로토콜 타입을 지정하고 있음을 알수 있다. Ethernet 헤더의 h_proto 와 IP 헤더의 ip_p 가 각 상위 Layer 의 프로토콜 타입을 알려주기 위해서 사용된다.

부연설명을 하자면 운영체제가 패킷을 받으면 가장 먼저 Link 레이어를 거치는데, Link 레이어에서는 Ethernet 헤더를 분석해서 패킷이 Network 레이어 로 전달되는 패킷인지 확인해서 Network 레이어로 전달된다면 해당 패킷이 IP 패킷인지 아니면 ICMP, IGMP 와 같은 패킷인지를 검사한후 Network 레이어의 알맞은 처리루틴으로 보낼것이다. Network 레이어에서는 패킷을 받은 다음 자신의 프로토콜 헤더를 검사해서 이 패킷이 Transport 레이어로 전달되는 패킷인지 확인하고, Transport 레이어로 전달된다면 UDP 인지, TCP 인지를 확인한다음에 Transport 레이어의 적당한 처리루틴으로 패킷을 던질것이다. 최후에 는 TCP 헤더만 남게 되는데, TCP 헤더의 PORT 를 검사해서 어떤 어플리케이션에게 전달되어야 하는지를 최종 결정하게 된다.


--------------------------------------------------------------------------------

3.3.2절. u_char *pcap_next

                               

다음 패킷에 대한 포인터를 리턴한다.

우리는 이 패킷을 읽음으로써 패킷의 정보를 얻어올수 있다. 실지로 이 함수를 이용해서 패킷캡쳐와 관련된 모든 일을 할수 있다. 나머지 패킷캡쳐와 관련된 함수들은 (pcap_loop 같은) 이 함수의 기능 추가버젼 이라고 볼수 있다.


--------------------------------------------------------------------------------

3.3.3절. pcap_loop

                               

p 는 PCD 이며, cnt 는 패킷 캡쳐를 몇번에 걸쳐서 할것인지를 결정하기 위해서 사용한다. 만약 0이 지정되면 계속 패킷을 받아들이게 된다.

callback 는 패킷이 들어왔을때 실행하는 함수의 포인터이다. 보통은 패킷필터링과 관련된 함수가 실행될것이다.


--------------------------------------------------------------------------------

3.3.4절. pcap_dispatch

                               

pcap_loop 와 거의 비슷하다.


--------------------------------------------------------------------------------

3.4절. 패킷 필터링 관련 API
3.4.1절. pcap_compile

                               

들어오는 패킷을 필터링 해서 받아들이기 위해서 사용한다. 예를 들어 tcpdump 에서 port 80 으로 오는 패킷만을 캡쳐하기 위해서 다음과 같이 사용하는걸 보았을것 이다.
[root@coco /root]# tcpdump port 80
Kernel filter, protocol ALL, TURBO mode (575 frames), datagram packet socket
tcpdump: listening on all devices
                               

tcpdump 명령 실행시킬때 뒤에 준 옵션인 "port 80" 이 filter rule 이며, str 아규먼트를 통해서 전달된다.

fp bfp_program 구조체의 포인터이며 pcap_compile 에 의해서 채워진다. netmask는 로컬 네트의 netmask 이다.

filter rule 에 대한 내용은 tcpdump 의 man 페이지에 상세하게 나와 있으니 참고하기 바란다.


--------------------------------------------------------------------------------

3.4.2절. pcap_setfilter

                               

pcap_compile 을 통해서 지정된 필터를 적용시키기 위해서 사용되며, 앞으로 들어오는 패킷에 대해서는 이 필터룰에 의해서 필터링 된다.


--------------------------------------------------------------------------------

4절. 예제 코드
pcap 의 기본적인 API 를 살펴봤으니 직접 코드를 작성해 보도록 하겠다.

예제 : pcap_test.c



컴파일 방법은 아래와 같다.
[root@localhost pcap_test]# gcc -o pcap_test pcap_test.c -lpcap -I/usr/include/pcap
               

다음은 필자의 Linux 박스에서 테스트한 결과이다.
[root@localhost pcap_test]# ./pcap_test -1 "port 80"
DEV : eth0
NET : 192.168.100.0
MSK : 255.255.255.0
=======================
IP 패킷
Version     : 4
Header Len  : 5
Ident       : 51804
TTL         : 64
Src Address : 192.168.100.130
Dst Address : 218.234.19.87
Src Port : 4996
Dst Port : 80
45000034ca5c400040065cfbc0a86482
daea1357138400502e7a303b2e7cc456
801021f0badb00000101080a0014e136
22631d7b485454502f312e3120323030
204f

IP 패킷
Version     : 4
Header Len  : 5
Ident       : 41787
TTL         : 54
Src Address : 218.234.19.87
Dst Address : 192.168.100.130
Src Port : 80
Dst Port : 4996



--------------------------------------------------------------------------------

5절. 결론
이상 간단하게 libpcap 의 사용방법에 대해서 알아보았다. 이번 글에서는 libpcap 의 사용법에만 초첨을 맞추고 있는데, 다음번 강좌에서는 몇가지 "응용" 에 대해서 알아보도록 하겠다.

주석
[1] Intrusion Detection System 의 줄임말 이며, 침입탐지 시스템을 말한다. 네트웍 침입탐지를 위한 NIDS, 호스트 침입탐지를 위한 HIDS 로 나눌수 있다. 일반적으로 IDS 라고 하면 네트웍 침입 탐지 시스템을 말한다.



출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • UDP 프로그래밍의 기초 (0)2007/05/14
  • ICMP 프로그래밍 (0)2007/05/14
  • libpcap 프로그래밍 (0)2007/05/14
  • pcap 을 이용한 id,password 정보가져오기 (0)2007/05/14
  • SNMP개요및 설치,운용 (0)2007/05/11
2007/05/14 10:24 2007/05/14 10:24
Posted by webdizen
Tags HIDS, IDS, libpcap, NIDS, 패킷 캡처
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2935

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/14 10:07

pcap 을 이용한 id,password 정보가져오기

이글은 pcap 을 이용한 id, password 의 캡춰에 대한 내용을 담고 있다. 글의 후반부에는 이러한 패킷캡춰를 막을수 있는 몇가지 방법에 대한 내용도 포함되어 있다.


1절. 소개
2절. ID 및 패스워드 검색 프로그램
2.1절. 이거 크래킹 프로그램 아닌가요?
2.2절. 구현 방법
2.3절. id, password 전달문자열 확인하기
2.4절. 예제코드
2.5절. 정보 누출을 막는 방법

--------------------------------------------------------------------------------

1절. 소개
지난번에는 pcap 소개문서인 libpcap 프로그래밍 libpcap 프로그래밍을 통해서 기본적인 패킷캡쳐 방법에 대해서 알아보았었다.

이번에는 실질적인 응용에 대해서 간단한 예제와 함께 공부해 보도록 하겠다. 지난번 libpcap 에 대한 이해를 마쳤다면, 이 문서는 가벼운 마음으로 읽어나갈수 있을것이다.


--------------------------------------------------------------------------------

2절. ID 및 패스워드 검색 프로그램
이번에 만들고자 하는 응용은 LAN 상에서 특정 사이트에 접속하고자 하는 유저의 ID와 패스워드를 가져오는 프로그램이다.


--------------------------------------------------------------------------------

2.1절. 이거 크래킹 프로그램 아닌가요?
어떻게 생각하면 문제의 소지가 있는 프로그램이 아닐까라고 생각 되지도 하지만 어차피 tcpdump 를 어느정도 사용할줄 아는 유저라면 단순 tcpdump 분석을 하든지 아니면 이미 인터넷에 널려 있는 다른 프로그램들을 사용하든지, 암호화 통신을 하지 않는 사이트에 대한 ID, Password 는 손쉽게 가져올수 있을 것임으로 특별히 악용될것이라고는 생각되지 않는다.

그리고 어차피 이러한 기능은 상용으로 판매하는 여러 IDS 에서도 부가적으로 제공하는 기능들이다. 이런 점에서 봤을때 회사내부에서 암호화 통신을 하지 않는 사이트(혹은 telnet 연결)들을 돌아다니는 것은 대단히 위험하다. 마음만 먹으면 누구든지 ID 와 Password 를 간단히 빼낼수 있으며, 메신저등을 사용할때 그 내용역시 쉽게 도청될수 있다.

이러한 류의 크래킹 깁버은 일반적인 크래킹유형이며, 막는 방법도 널리 알려져 있다. 문서의 끝에서는 이러한 방법에 대해서도 알아보도록 할것이다.


--------------------------------------------------------------------------------

2.2절. 구현 방법
libpcap 에 대한 문서를 읽어보고 거기에 대한 이해를 한상태라면 이런 프로그램은 구현이라고 말할 성질의 것도 아니다. 단지 http 상에서 ID 와 PASSWORD 를 넘길때 어떻게 넘기는지 에 대한 이해가 있고, 문자열을 다루기 위한 C의 표준 문자열관련 함수만 알고 있다면 구현은 식은죽 먹기이다. 남은건 다만 어떻게 좀더 효율적으로 원하는 정보(id, password)를 간추려서 보여줄것인가 하는 고민 정도이다.

여기에서는 아주 원시적인 방법을 사용할것이다. Id 와 password 를 빼내오기를 원하는 타켓 사이트를 정하고 (여기에서는 www.joinc.co.kr 을 타겟 사이트로 할것이다 -.-;) 해당 사이트에서 ID 와 Password 입력후 로그인 버튼을 클릭했을때 어떤 특정한 데이타가 전달되는지를 확인후, 그 데이타를 포함한 패킷을 캡쳐하도록 코딩하는 것이다. 비록 원시적인 방법이긴 하지만 원리를 설명하는데에는 부족함이 없을 것으로 생각된다.


--------------------------------------------------------------------------------

2.3절. id, password 전달문자열 확인하기
Post 방식으로 전달되는데, 이때 정확하게 어떤 방식으로 전달되어야 하는지 알아야될 필요가 있다. www.joinc.co.kr 의 HTML 페이지 쏘스를 보면 id 는 uname, 패스워드는 pass를 통해서 전달되는걸 확인할수 있다.

libpcap 프로그래밍의 예제프로그램을 이용해서 id 와 password 입력시 서버측에 어떤 메시지가 전달되는지 확인하고 해당메시지에 포함된 문자열만을 추출해 내면 된다. 필자가 본인의 사이트를 확인해본결과(-.-) 다음과 같이 전달됨을 확인할수 있었다.

....
....
uname=xxxxx&pass=yyyy&....
....


그럼으로 uname=xxxx&pass=yyyy 문자열을 만나면, 해당 사이트로 아이디와 패스워드가 전달되는 것으로 판단하고 xxxx 와 yyyy 를 추출해내면 될것이다.


--------------------------------------------------------------------------------

2.4절. 예제코드
여기에 있는 코드는 일반적인 코드는 아니고, www.joinc.co.kr 에 대해서만 적용시킬수 있는 코드이다. 좀더 일반적인 코드로 만드는건 각자의 몫이다. 마찬가지로 예제코드는 구현가능여부에 촛점을 맞춘 코드로써 효율적인 측면등은 고려하지 않았다.

코드는 libpcap 에 대한이해를 하고 있다면 어렵지 않게 이해 가능할것임으로 설명은 코드에 있는 주석으로 대신하도록 하겠다.

예제 : pass_capture.c



다음은 위의 프로그램을 이용해서 실제로 아이디와 패스워드를 캡쳐한 결과이다.
[root@localhost pcap_test]# ./pass_capture -l "port 80"
DEV : eth0
NET : 211.244.233.0
MSK : 255.255.255.0
=======================
HEADER INFO
Src Address : 211.244.233.55
Dst Address : 218.234.19.87
Src Port : 33189
Dst Port : 80
Uname :
Pass :
======================

HEADER INFO
Src Address : 218.234.19.87
Dst Address : 211.244.233.55
Src Port : 80
Dst Port : 33189
Uname :
Pass :
======================


위의 프로그램은 문자열을 이용해서 아이디와 패스워드를 캡쳐해내고 있는데, 여기에 port, ip 등의 조건을 입력한다면 좀더 정확한 결과를 얻을수 있을것이다. 이 프로그램은 LAN 상에 묶여있는 모든 호스트에서 발생하는 패킷에 대한 검사를 수행하게 될것이다(스위칭 라우터(허브)에 의해서 관리 되지 않는다는 가정하에).


--------------------------------------------------------------------------------

2.5절. 정보 누출을 막는 방법
위의 코드를 보면 알겠지만, 마음만 먹는다면 누군가의 정보를 가져오는게 그리 어려운일이 아니라는걸 알수 있다. 이 말은 역으로 여러분의 정보 역시 쉽게 도청당할 수 있음을 뜻하기도 한다. 위의 코드의 약간의 응용만으로도 TELNET, 메신져, MAIL 등의 정보도청이 가능할것이다.

이러한 패킷캡쳐에 의한 정보누출을 가장 간단한 방법은 스위칭 라우터 혹은 스위칭 허브를 이용해서 로컬네트웍을 세그먼트 단위로 나누는 방법이될것이다. 이경우 패킷은 전달되어야 하는 세그먼트로만 전송될것임으로 LAN 상의 다른 호스트에서 패킷을 훔쳐볼수 없을것이다(보안적인 측면외에도 로컬 네트웍 트래픽을 줄일수 있다는 장점도 가진다).

그러나 스위칭 라우터(허브)를 이용하는것은 완벽한 방법이아니다. 몇몇 알려진 sniffing 기법에 의해서 어렵지 않게 패킷을 훔쳐볼수 있기 때문이다(네트웍 보안의 기본(1) 을 참고하라). 현재 생각할수 있는 가장 훌륭한 정보누출을 막는 방법은 암호화 통신이 될것이다. telnet 혹은 http 대신에 SSL 을 이용하는 ssh 나 https 서비스를 사용해서 패킷을 암호화 하거나, 메일전송시 PGP 등을 이용해서 메일내용을 암호화해서 보내는 방법등이 있을것이다.


출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • ICMP 프로그래밍 (0)2007/05/14
  • libpcap 프로그래밍 (0)2007/05/14
  • pcap 을 이용한 id,password 정보가져오기 (0)2007/05/14
  • SNMP개요및 설치,운용 (0)2007/05/11
  • SNMP응용 프로그램 제작 (0)2007/05/11
2007/05/14 10:07 2007/05/14 10:07
Posted by webdizen
Tags pcap, 크래킹
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2934

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/11 10:40

SNMP개요및 설치,운용

SNMP는 NMS(네트워크 관리 시스템)을 위해서 가장 널리 사용되는 프로토콜이다. 이글은 SNMP의 개념과 개념의 이해를 위해서 실제로 널리사용되는 snmp애플리케이션을 실제 설치해서 운용테스트를 해볼것이다. 이 문서는 사용자정의 snmp애플리케이션을 제작하기 위한 기초문서가 될것이다.

1절. 소개
2절. SNMP개요
2.1절. SNMP란 무엇인가
2.2절. SNMP로 할수 있는 것들
2.3절. SNMP를 통한 망의 구성
2.4절. MIB에 대해서
2.5절. SNMP 프로토콜의 동작과 구성
3절. SNMP 설치 및 운용
3.1절. ucd-snmp 설치
3.2절. SNMP AGENT 실행
3.3절. SNMP MANAGER 테스트
3.3.1절. 동기적인 데이타 요청 - snmp get, get next
3.3.2절. 비동기적인 데이타 요청 - snmp trap
4절. 결론

--------------------------------------------------------------------------------

1절. 소개
개인적으로 최근들어 SNMP에 관심을 가지게 되었다. (실은 상당히 오래되었지만) 그래서 앞으로 몇부? 에 걸쳐서 SNMP관련 강좌를 개설하고자 한다. 강좌는 SNMP개요및 설치운용에서 부터 시작해서 프로그래밍을 통해서 SNMP응용 애플리케이션을 제작하고, 확장 MIB(뒤에 설명한다)를 작성하는 것 까지를 다룰것이다.

이번글은 그중 첫번째 글로 SNMP개요와 설치및 운용에 대한 글이다. 설치및 운용은 실제 어떻게 작동되는지 눈으로 확인하는 차원의 수준에서 이루어질 것이며, 설치되는 snmp애플리케이션의 상세설치와 높은 수준에서의 운용에 대해서는 언급하지 않을것이다. 이러한 것들은 (필요할경우)해당 snmp애플리케이션의 메뉴얼을 참고해서 개인적으로 학습해야만 할것이다.

여기에서 얻은 지식은 나중에 SNMP애플리케이션을 제작하는 밑거름이 될것이다.


--------------------------------------------------------------------------------

2절. SNMP개요
2.1절. SNMP란 무엇인가

SNMP는 Simple Network Management Protocol의 약자이다. 해석을 해보자면 간단한 네트워크관리를 위한 규약 인데, 말그대로 SNMP는 네트워크관리를 위한 용도로 사용되는 프로토콜이다. 가장 앞에 Simple라는 단어가 붙어있는데, 진짜로 간단한 프로토콜인지 아닌지는 사람에 따라 약간씩 차이가 있을수 있다. 필자가 보기엔 그리 복잡한 프로토콜은 아닌것 같은데, 어떤 사람들은 매우 복잡한 프로토콜 이라고 말하는 사람들도 있다.

그럼 먼저 SNMP가 나타난 배경에 대해서 알아보도록 하겠다. SNMP가 쓰이기 전에 일반적으로 사용되는 네트워크 관리는 ICMP에 의존했었다. ICMP는 Network계층의 프로토콜로써, 운영체제에 관계없이 사용할수 있는 간단한 프로토콜이였다. 이 프로토콜을 이용해서 우리는 네트워크로 연결된 각각의 호스트가 작동하고 있는지, 작동한다면 어느정도의 응답시간을 가지고 작동하는지 등의 간단한 정보를 얻을수 있었으며, 초기에는 이정도로도 필요한 네트워크 관리가 가능했었다. ICMP를 이용한 가장 유용한 도구는 아마도 ping 프로그램일 것이다.

그러나 인터넷의 사용이 보편화되고 네트워크에 연결된 호스트의 수가 증가하자 거기에 따라서 네트워크 구성역시 복잡해지고, ICMP만을 가지고는 이러한 네트워크의 관리를 효율적으로 할수 없게 되었다.

그래서 몇가지 프로토콜에 대한 연구가 진행되었고, SGMP, HIMS, CMIP/CMIS등이 제안되게 되었다. 이중에서 SGMP를 발전시킨 SNMP가 사실상 네트워크 관리를 위한 표준적인 프로토콜로 자리잡게 되었다. 다른 프로토콜들이 사용되지 않은데에는 몇가지 이유가 있었다. CMIP/CMIS는 너무 방대하고 너무 복잡했으며, HEMS의 경우에는 실제 적용사례가 적었기 때문이다.

어쨋든 SNMP는 거의 대부분의 운영체제에서 사용되어 지고 있다. 여러분이 사용하는 Linux, 그밖의 대부분의 유닉스와, 윈도우계열 운영체제는 기본적으로 SNMP프로토콜을 사용하는 도구들을 제공하고 있다. 그외에도 router등 TCP/IP를 네트워크 프로토콜로 사용되는 운영체제들 역시 SNMP는 필수적으로 제공하고 있다.


--------------------------------------------------------------------------------

2.2절. SNMP로 할수 있는 것들
SNMP를 이용해서 할수 있는 것들은 다음과 같다.


네트워크 구성관리
네트워크상의 호스트들이 어떤 구조를 이루고 있는지 지도를 그리는게 가능하다.

성능관리
각 네트워크 세그먼트간 네트워크 사용량, 에러량, 처리속도, 응답시간 등 성능 분석에 필요한 통계정보를 얻어낼수 있다.

장비관리
SNMP의 주목적이 네트워크관리관리 이기는 하지만 SNMP특유의 유연한 확장성을 이용하여서 시스템정보(CPU, MEMORY, DISK 사용량)의 정보를 얻어올 수 있도록 많은 부분이 확장되었다. 이 정보는 네트워크문제를 해결하는데 큰도움을 준다. 예를들어 특정 세그먼트의 네트워크 사용량이 갑자기 급증했는데, 특정 호스트의 CPU사용율까지 갑자기 증가했다면, 우리는 해당 호스트에서 문제가 발생했을것이란걸 유추해낼수 있을것이다.

보안관리
정보의 제어 및 보호 기능, 최근버젼인 SNMP3는 특히 정보보호를 위한 기능이 향상되었다.



--------------------------------------------------------------------------------

2.3절. SNMP를 통한 망의 구성
SMTP는 인터넷상에서 메시지를 교환하기 위한 프로토콜로 사용되며, 주로 전자메일 교환을 위해서 사용되는 프로토콜이다. 그러나 SMTP는 어디까지나 프로토콜일 뿐이며, 실제 메시지를 인터넷상에서 주고 받기 위해서는 SMTP프로토콜을 사용하는 SMTP서버(Sendmail같은)와 SMTP클라이언트(mutt, pine같은)가 준비되어 있어야만 한다.

SNMP역시 그자체로는 프로토콜일 뿐이며 SNMP프로토콜을 활용해서 실제 네트워크 관리 정보를 얻어오기 위해서는 응용 애플리케이션이 준비되어있어야만 한다. 보통의 네트워크프로토콜을 사용하는 애플리케이션이 서버/클라이언트 모델로 구성되듯이 SNMP역시 서버와 클라이언트로 구성된다.

사용자 삽입 이미지
그림 1. SNMP망 관리 시스템


일반적으로 SNMP망 에서는 서버/클라이언트라고 부르지 않고 snmp manager/snmp agent라고 부른다. snmp agent는 관리대상이 되는 시스템에 설치되어서 필요한 정보(네트워크 혹은 시스템)를 수집하기 위한 snmp 모듈(혹은 애플리케이션) 이며, snmp manager은 snmp agent가 설치된 시스템에 필요한 정보를 요청하는 snmp 모듈이다. snmp agent는 서버, snmp manager은 클라이언트로 생각하면 이해하기가 좀더 수월할 것이다(그러나 반드시 agent가 서버, manager이 클라이언트가 되는건 아니다. 그냥 개념적으로 이해만 하고 있도록 하자).


--------------------------------------------------------------------------------

2.4절. MIB에 대해서
SNMP는 네트워크를 관리하기 위한 프로토콜이다. 그렇다면 무엇을 관리할 것인가(관리객체)를 결정해야 할것이다. 관리객체를 결정했다면, 이러한 관리객체를 효과적으로 관리하기 위해서 이를 분류해야 할것이다. 이게 바로 MIB이다.

MIB는 Man In Black의 줄임말이 아니다. Management Information Base의 줄임말인데, 관리되어야할 자원 객체의 분류된 정보를 말한다. 관리되어야할 객체는 시스템정보, 네트워크사용량, 네트워크 인터페이스정보 등이 된다.

이 MIB객체들은 관리하기 편하도록 Tree구조를 가지게 된다. 다음은 MIB의 일반적인 구조이다.
사용자 삽입 이미지


MIB는 위에서 처럼 계층적인(디렉토리) 구조를 가지게 된다(위의 그림은 MIB를 설명하기 위해 일부만을 표시하고 있다). 예를들어서 agent가 설치되어 있는 시스템으로 부터 시스템부가정보(sysDescr)를 얻어오길 원한다면 ISO.org.dod.internet.mgmt.mib-2.system.sysDescr과 같은 식으로 manger에서 데이타를 요청하면 된다.

위의 MIB계층 구조를 보면 각 MIB옆에 숫자가 있는것을 볼수 있다. 이 숫자가 OID번호이다. 즉 sysDescr의 OID값은 1.3.6.1.1.2.1.1.1 이 될것이다. OID번호를 이용하는 이유는 MIB고유 문자열을 통해서 원하는 데이타를 가져오기위해서는 아무래도 요청이 길어질수가 있기 때문이다.

MIB는 IANA(Internet Assigned Number Authority)라는 단체에서 관리하며 표준적으로 사용되고 있다. 그럼으로 표준적인 MIB구현을 위해서는 IANA에서 OID를 부여받아야만 한다. 그래야 전체네트워크상에서 다른 여러가지 MIB와 중복되지 않고 사용이 가능할것이다.

작은 정보: cisco과 같은 대중적인(거의 표준이나 마찬가지인) 제품들은 모두 자체적인 MIB를 구현해서 IANA에 등록하여 사용하고 있다. 여러분이 cisco 라우터등의 SNMP정보를 접근할수 있다면 cisco MIB가 등록되어 있음을 확인할수 있을것이다. 확인하는 방법은 다음 강좌에서 따로 언급하도록 하겠다.


MIB는 계층적 구조를 가짐으로 필요에 따라서 확장해서 사용이 가능하며, (물론 프로그래밍 능력이 있어야 하지만)때에 따라서는 자체 회사내에서만 사용가능하거나 제한된 네트워크 영역의 네트워크상황을 관제하는 제품을 위한 MIB를 추가해야 하는경우가 생길수 있을것이다. 그래서 사설로 MIB를 만들어서 사용할수 있는 여지를 남겨두었다. (마치 독립된 지역네트워크를 위해 사설IP를 사용하는 것처럼) 이러한 사설 MIB는 private(4)의 enterprises(1)에 정의해서 사용할수 있다. 여러분이 그리 대중적이지 않은 그래서 IANA에 등록되지 않은 어떤 장비의 고유 SNMP정보를 얻어오고 싶다면 업체에 문의하거나, 메뉴얼을 확인하는 정도로 쉽게 SNMP정보를 얻어올수 있다.

현재 MIB는 버젼 2까지나와 있으며, 버젼의 구분을 위해서 MIB-1, MIB-2로 부르고 있다. MIB-2는 MIB-1의 확장판으로 MIB-1의 모든 객체를 포함하여 약 171개의 객체들을 더 포함하고 있다. 최근의 제품들은 대부분 MIB-2를 지원하고 있다. 물론 위에서 말했듯이 독자적인 MIB를 만들어서 사용할수 있으며, 이를 확장 MIB라고 부른다.


--------------------------------------------------------------------------------

2.5절. SNMP 프로토콜의 동작과 구성
현재 SNMP는 버전 3가지 나와있는 상태이지만 아직까지는 버젼2가 가장 널리 사용 되고 있다. 필자역시 SNMP 버젼 2에 대한 경험이 많은 관계로 버젼2를 기준으로 설명하도록 하겠다.

SNMP는 기본적으로 네트워크 정보를 수집하는데 그 목적이 있는데, 수집하는 몇가지 각각 다른 방법이 있다. 일반적으로 생각해서 우리가 생활중에 얻게 되는 정보는 우리가 요구해서 발생하는 정보와(신문을 구입한다든지, 인터넷으로 서핑을 하는등) 뉴스속보와 같은 형식으로 중요한 일이 있을때 발생하는 정보가 있을것이다. 또한 단지 정보를 얻는데 그치지 않고 정보를 입력하기도 한다.

SNMP정보수집역시 기본적으로 위의 일상생활에서의 정보수집과 같은 방식으로 이루어진다. 이하 snmp manager은 manager로 snmp agent는 agent로 부르도록 한다.


GET
manager에서 agent로 특정 정보를 요청하기 위해서 사용한다.

GET NEXT
기본적으로는 GET과 같은일을 한다. 그러나 SNMP에서 각정보들은 계층적 구조로 관리된다. 위의 MIB계층 구조를 나타낸 이미지에서 우리는 system(1)계층밑에 있는 모든 정보를 가져오고 싶을 때가 있을것이다. 그럴경우 GET NEXT를 사용할수 있다.

SET
manager에서 agent로 특정 값을 설정하기 위해서 사용한다.

TRAP
agent에서 통보해야될 어떤 정보가 발생했을때(임계치를 넘는네트워크자원 사용등) manager에게 해당 상황을 알리기 위해서 사용한다. 위의 다른 요청들이 동기적 요청이라면 이것은 비동기적 사건을 알리기 위해서 사용되어진다.


SNMP프로토콜은 기본적으로 어떤 정보를 요청하는 메시지와 이에 대한 응답메시지로 이루어지며 다음과 같은 구조를 가지고 있다.

표 1. SNMP 메시지

Version Community name SNMP PDU

Version은 말이 필요없다. SNMP프로토콜의 버젼번호를 나타낸다. Community name은 메니저와 에이전트간의 관계를 나타내는데, 인증, 접근통제등의 목적으로 사용된다. 보통은 간단하게 public을 사용한다. PDU 는 Physical Data Unit의 줄임말인데, 실제 전송되는 필요한 정보들을 담고 있는 Unit이다. Unit 이라고 하는 이유는 실제 전송되는 정보들의 부가 속성을 나타내기 위한 몇가지 값들을 포함하고 있기 때문이다. PDU는 PDU 타입(GET인지 Set인지 GET Next인지, TRAP인지등)과, Request-id, 실제보내고자 하는 데이타등(OID와 OID에 대한 값들)으로 구성되어 있다.

SNMP를 통해서 전달되는 메시지들은 기본적으로 UDP를 이용하게 된다. 바로위에서 PDU는 Request-id를 포함하고 있다고 했는데, 데이타그램처리방식인 UDP의 단점을 극복하기 위해서 사용되는 값으로, 각 메시지의 요청번호를 표시한다. 그래야만 수신된 SNMP메시지가 어떤 요청에 대해서 수신된 메시지인지 확인이 가능할것이기 때문이다.


--------------------------------------------------------------------------------

3절. SNMP 설치 및 운용
그럼 실제로 시스템에 SNMP(agent와 manager 애플리케이션)을 설치해서 정보를 가져오는걸 간단히 테스트 해보도록 하겠다.

설치는 Linux(Kernel-2.4.x)에서 ucd-snmp로 할것이다. 위에서 설명했듯이, SNMP는 manager과 agent로 운영되게 되는데, 테스트의 편의를 위해서 하나의 시스템(localhost)에서 manager와 agent를 운용하도록 하겠다.


--------------------------------------------------------------------------------

3.1절. ucd-snmp 설치
ucd-snmp는 net-snmp.sourceforge.net에서 얻을수 있으며 애플리케이션 관련 정보들도 얻을수 있다. ucd-snmp는 현재 버젼 5.x대까지 진행되어 있는데, 5.x부터는 net-snmp로 이름을 바꾸고 개발되어지고 있으며, 4.x버젼까지를 ucd-snmp라고 부르고 있다. 필자는 익숙한 ucd-snmp(버젼 4.x)를 설치하도록 할것이다. 비록 net-snmp가 최신이긴 하지만 별로 다루어본적이 없고, 대부분의 경우 아직까지는 ucd-snmp가 많이 사용되어지고 있기 때문이다. 최신이 아니라고 불만을 가질 필요는 없다. 근본적으로 net-snmp와 ucd-snmp간의 차이는 없으며, 우리의 목적은 최신의 snmp애플리케이션을 테스트하는게 아닌 snmp의 기능과 원리를 이해하고 이를 이용해서 필요한 응용 애플리케이션을 작성하는 것이기 때문이다.

위의 URL에서 ucd-snmp를 다운받아서 압축을 풀고 컴파일 하도록 하자. 컴파일 하는중에는 아마도 아무런 문제가 없을것이다. 컴파일은 매우 일반적인 방법을 따른다. 적당한 디렉토리에 압축을 풀고 ./configure, make, make install 하면된다.
[root@localhost src]# tar -xvzf ucd-snmp-4.2.6.tar.gz
[root@localhost src]# cd ucd-snmp-4.2.6
[root@localhost ucd-snmp-4.2.6]# ./configure
[root@localhost ucd-snmp-4.2.6]# make
[root@localhost ucd-snmp-4.2.6]# make install


헤에... 너무 간단하지 않은가 ?


--------------------------------------------------------------------------------

3.2절. SNMP AGENT 실행
make install 까지 했다면 agent와 manager프로그램이 모두 설치되어 있을 것이다. 그리고 여기에 더불어 개발자를 위한 각종 라이브러리와 헤더파일들도 설치된다. 이 라이브러리와 헤더파일들은 개발할때 필요하며 다음 강좌에서 다루게 될것이다.

ucd-snmp는 agent 프로그램으로 snmpd를 제공한다. agent환경을 제대로 만들려면 복잡해보이는(사실은 그리 복잡하다고 볼수없는) 설정파일을 만들어줘야 하지만 이것은 각자의 몫이다. net-snmp프로젝트 홈페이지에서 제공하는 메뉴얼을 참고하기 바란다. 어쨋든 현재로써는 단지 snmpd를 띄우는 정도로 snmp agent환경을 만들수 있다. [root@localhost root]# snmpd


이것으로 snmp를 테스트할 최소한의 agent환경이 구축되었다.


--------------------------------------------------------------------------------

3.3절. SNMP MANAGER 테스트
3.3.1절. 동기적인 데이타 요청 - snmp get, get next
GET과 GET NEXT는 동기적인 정보요청을 위해서 사용한다. manager에서 agent에 대해서 정보를 요청했을때 해당 정보를 agent에서 보내주는 방식이다. GET은 단일정보요청을 위해서 사용하며, GET NEXT는 해당 계층의 하위에 있는 모든 정보의 요청을 위해서 사용된다.

ucd-snmp는 이러한 정보요청을 위한 manager프로그램으로 snmpget과 snmpnext, snmpwalk를 제공한다.

snmpget은 이름에서 알수 있듯이 agent로부터 특정한 정보를 얻어내기 위해서 사용한다. 정보를 얻기 위해 필요한 기본정보는 agent가 설치되어 있는 서버의 주소(혹은 이름) 와 커뮤니티(권한을 위한)이름 그리고 얻기 원하는 정보의 OID번호 혹은 MIB의 계층이름이다. 예를들어서 localhost로부터 public권한을 가지고 sysDescr(시스템 부가정보)정보를 얻어오고 싶다면 아래와 같이 하면 된다.
[root@localhost /root]# snmpget localhost public system.sysDescr.0
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686


혹은 MIB이름대신에 OID번호를 사용해도 된다.
[root@localhost /root]# snmpget localhost public 1.1.0
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686




snmpwalk는 해당 MIB의 하위계층에 있는 모든 정보를 요청한다. 예를들어 system MIB의 하위 계층에 있는 모든 OID에 대한 정보를 요청하길 원한다면 아래와 같이 하면된다. 이게 가능한 이유는 snmpwalk가 정보를 요청하기 위해서 snmp메시지를 만들때 PDU타입을 GET NEXT를 사용하기 때문이다. 나중에 직접구현하게 될것이다. 지금은 구현에 신경쓰지 말자.

[root@localhost /root]# snmpwalk localhost public system
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
system.sysObjectID.0 = OID: enterprises.ucdavis.ucdSnmpAgent.linux
system.sysUpTime.0 = Timeticks: (2685699) 7:27:36.99
system.sysContact.0 = yundream@joinc.co.kr
system.sysName.0 = localhost
system.sysLocation.0 = myhome
system.sysORLastChange.0 = Timeticks: (0) 0:00:00.00
....


system하위의 모든 OID에 대한 정보를 얻어오고 있음을 확인할수 있다.

snmpgetnext는 snmpwalk의 기능 축소판정도로 볼수 있을것이다. 즉 MIB계층구조에서 현재 요청한 OID의 다음 OID의 정보를 가져온다. 예를들어 system.sysDescr.0에 대한 정보를 요청하면 다음 OID인 system.sysObjectID.0의 정보를 요청하게 될것이다. 이게 가능한 이유는 snmpwalk와 마찬가지로 내부적으로 GET NEXT를 이용하고 있기 때문이다. snmpwalk가 더이상 얻을수 없을때까지 OID를 요청하는것과 달리 snmpgetnext 바로다음의 OID만을 요청한다.
[root@localhost /root]# snmpgetnext localhost public system system.sysDescr.0
system.sysDescr.0 = Linux localhost 2.4.2-2 #1 Sun Apr 8 20:41:30 EDT 2001 i686
system.sysObjectID.0 = OID: enterprises.ucdavis.ucdSnmpAgent.linux





--------------------------------------------------------------------------------

3.3.2절. 비동기적인 데이타 요청 - snmp trap
기본적으로 GET, GET NEXT를 통한 데이타요청은 일정한 polling시간을 가지고 manager에서 agent로 필요한 정보를 요청하는 방식이다. 그러나 이걸 이용해서는 비동기적으로 발생하는 정보를 수집할수가 없다.

이러한 비동기적인 정보는 여러가지가 될수 있다. 예를들면 특정 네트워크 세그먼트에 문제가 생겼다거나 디스크나 메모리용량을 과다하게 사용하고 있다거나(많은 운영체제의 경우 시스템정보까지도 snmp를 통해서 얻을수 있도록 허용하고 있다)하는 사건들은 비동기적으로 발생할것이다. 이럴경우에는 agent에서 manager측으로 사건을 통보해야 할것이다. 이렇게 agent에서 manager측으로 비동기적으로 사건을 통보하는 것을 SNMP TRAP라고 한다(간단히 말해서 경고메시지 보내는거다).

ucd-snmp에서는 이러한 trap정보를 전송하고 받기 위해서 snmptrapd와 snmptrap를 제공한다. snmptrapd는 agent에 제공되는 데몬프로그램으로 manager에서의 trap데이타 발생을 기다린다. snmptrap는 agent에 설치되어서 사용될수 있으며 trap데이타를 manager로 전송하는 일을한다.

이 snmptrap은 꽤 유용하게 사용할수 있다. 간단하게 스크립트로 만들어서 어떤 파일이 변조되었을경우 trap정보를 manager쪽으로 발생시킨다거나, 프로세스 갯수가 일정갯수 이상 초과했을때 이를 전송한다든지 하는 기능을 비교적 간단하게 추가시킬수 있을것이다.

다음은 ucd-snmp에서 제공하는 trap애플리케이션을 이용한 간단한 테스트이다. 먼저 snmptrapd를 manager측에서 실행시켜야 한다. 이 애플리케이션은 옵션없이 실행할경우 데몬모드로 실행되며 표준출력을 시키지 않음으로 다음과 같이 옵션을 주고 실행시켜서 일반모드(forground)에서 받은 trap정보를 표준출력하도록 실행시키도록 하자.
[root@localhost root]# snmptrapd -f -P
2003-04-23 00:13:34 UCD-snmp version 4.2.6 Started.


이제 agent측에서 snmptrap를 이용해서 trap정보를 manager로 전송해보도록 하자.
[root@localhost root]# snmptrap -v 2c -c public localhost "" ucdStart sysContact.0 s "yundream"


그러면 manager로 system.sysContact.0="yundream" 과 같은 정보가 전달되는걸 확인할수 있을것이다.

이들 ucd-snmp에서 제공하는 애플리케이션들의 자세한 사용법은 메뉴얼 페이지를 참고하기 바란다.


--------------------------------------------------------------------------------

4절. 결론
이상 SNMP의 개념과 개념의 이해를 위해서 실제 사용되는 snmp애플리케이션을 설치해서 간단히 운영테스트까지 해보았다. 이러한 운영테스트를 위해서 ucd-snmp를 사용했는데, 다음 강좌는 ucd-snmp에서 제공하는 snmplib를 통해서 snmp애플리케이션을 만드는 법을 다루도록 하겠다.



출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • libpcap 프로그래밍 (0)2007/05/14
  • pcap 을 이용한 id,password 정보가져오기 (0)2007/05/14
  • SNMP개요및 설치,운용 (0)2007/05/11
  • SNMP응용 프로그램 제작 (0)2007/05/11
  • libpcap을 이용한 포트스캐닝 검사 (2)2007/05/11
2007/05/11 10:40 2007/05/11 10:40
Posted by webdizen
Tags MIB, SNMP, ucd-snmp
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2929

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/11 10:33

SNMP응용 프로그램 제작

SNMP를 이용하면 네트워크와 시스템 관리를 위한 유용한 정보를 쉽게 얻어올 수 있다. 이번 기사는 SNMP를 이용해서 네트워크 정보를 얻어오는 방법을 예제를 통해서 제시하고있다.


1절. 소개
2절. SNMP 애플리케이션 작성
2.1절. 준비사항
2.2절. 예제프로그램을 통한 이해
2.3절. 다른 예제 : 비동기 데이타 요청
3절. 결론

--------------------------------------------------------------------------------

1절. 소개
지난번 기사에서 간단하게 SNMP의 개요와 설치및 운용방법에 대해서 알아보았다. 이번에는 실제 SNMP응용프로그램, 더 정확히 말해서 SNMP Manager를 작성하는 내용을 다루게 된다.

이 강좌의 내용은 Tutorial형식을 따르게 될것이다. 어디까지나 SNMP를 응용해서 애플리케이션을 작성하는것에 촛점을 맞출것이며, SNMP프로토콜 차원의 상세한 내용에 대해서는 다루지 않을것이다. 어쨋든 이 문서를 통해서 SNMP애플리케이션 제작의 기본적인 아이디어를 얻을수 있을것이다.


--------------------------------------------------------------------------------

2절. SNMP 애플리케이션 작성
2.1절. 준비사항
먼저 접근할수 있는 SNMP agent가 준비되어 있어야 한다. 자신의 호스트에 설치되어 있다면 테스트하기 편하겠지만 , 자신의 호스트에 설치되어 있지 않다고 하더라도 접근할수 있는 snmp agent가 설치된 호스트가 있으면 관계없다.

이책의 모든 내용은 기본적으로 ucd snmp 4.x를 기준으로 작성되어있다. ucd snmp의 설치와 운영방법에 대해서는 SNMP개요및 설치,운용기사를 참고하기 바란다.

여러분이 ucd snmp를 성공적으로 설치했다면, snmp개발 라이브러리와 헤더파일들이 설치될 것이다. snmp애플리케이션을 만들기 위해서는 이들 라이브러리와 헤더파일의 위치를 알고 있어야 한다. ucd snmp를 컴파일할때, --prefix 옵션을 사용하지 않았다면, 기본설치디렉토리인 /usr/loca/lib, /usr/local/include/ucd-snmp에 설치될것이다. 각자 확인해보기 바란다.


--------------------------------------------------------------------------------

2.2절. 예제프로그램을 통한 이해
앞서 이 문서는 tutorial형식을 따른다고 했음으로, 우선 간단한 예제프로그램을 만들어보고, 예제프로그램을 설명하는 식으로 snmp플밍에 대해서 알아보도록 하겠다.

우리가 만들고자 하는 애플리케이션은 인자로 OID를 주면 agent로 연결해서 OID에 대한 값을 가져오는 프로그램으로 가장단순하지만 snmp플밍의 기본적인 골격을 가지고 있음으로 이 예제를 잘 이해하게 되면 다른 응용도 가능할것이다.

예제 : simple_snmp.cc

                       

코드자체를 이해하는 데에는 별어려움이 없을것이다. 위의 snmp응용 애플리케이션을 제작하는 전형적인 모습을 보여준다.


즉 가정 먼저 snmp agent(이하 에이전트)와 연결을 맺기 위한 세션초기화 작업 으로 여기에는 호스트이름과, 커뮤니티이름, 그리고 SNMP버젼정보가 들어가게 된다. SNMP버젼은 다음과 같이 준비되어 있다.

표 1. SNMP 버젼 정의 상수

SNMP_VERSION_1 snmp 버젼 1
SNMP_VERSION_2c snmp 버젼 2
SNMP_VERSION_2u 정의만되어 있고 사용되어지지는 않는다.
SNMP_VERSION_3 snmp 버젼 3

세션을 맺고자 하는 에이전트의 snmp 버젼지원여부와 어느 버젼에서 지원하는 snmp정보를 가지고 올것인지를 확인해서 적당한 버젼으로 선택하면 된다. 보통 SNMP_VERSION_1 혹은 SNMP_VERSION_2c를 사용하면 문제없으며, SNMP_VERSION_2c를 사용하는게 가장무난하다. snmp3에서 포함하는 다른기능들(보안문제, 혹은 확장된 다른정보들)을 이용하지 않는다면 SNMP_VERSION_2c를 사용하도록 하자.

snmp_sess_init()함수를 사용한다.

위에서 만든 세션정보를 사용해서 세션을 연다(open).

snmp_open()함수를 사용한다.

여러분이 에이전트로 부터 정보를 요청하기 위해서는 PDU를 작성해야한다. PDU에는 요청하고자 하는 정보의 OID, 요청종류(GET, GET NEXT, SET등)를 명시해야만 한다.

사용되는 함수는 snmp_pdu_create()와 read_objid()가 있다.

PDU를 작성했다면, 이제 에이전트측에 SNMP요청(패킷)을 보내야 할것이다. 요청을 위해서 snmp_synch_response()함수가 제공된다. 이것은 동기적인 요청을 위해서 사용한다. 비동기적으로 요청할수도 있는데, 이것은 따로 언급하도록 하겠다.

응답이 왔다면 응답데이타를 분석해야 할것이다. 응답데이타는 snmp_pdu 구조체형태로 넘어오게 된다. 응답데이타를 분석할때 중요한 것은 바로 데이타의 타입정보이다. snmp정보를 보면 어떤것은 일반 문자열인 반면 어떤것은 숫자, 혹은 시간등의 정보를 가진다. 이의 구분을 위해서 snmp_pdu구조체에는 해당 데이타의 타입정보를 가지는 변수가 지정되어 있다. 주로 사용되어지는 데이타타입에는 다음과 같은것들이 있다.

표 2. SNMP 데이타 타입

ASN_OCTET_STR 일반 문자열
ASN_GAUGE unsigned 32bit int
ASN_INTEGER signed 32bit int
ASN_IPADDRESS 32bit 인터넷주소
ASN_COUNTER unsigned 32bit int
ASN_TIMETICKS unsigned 32bit int
ASN_OID OID 문자열


위의 SNMP데이타 타입표에서 ASN_TIMETICKS의 경우 1/100초 단위이다. 예를 들어 시스템의 Up타임을 가져오기 위해서 simp_snmp를 실행시켰는데, 다음과 같은 결과가 나왔을 경우  
[root@localhost test]# ./snmp3 system.sysUpTime.0
value #1 is a INTEGER: 32172
                       

321.72초가 된다.

이제 위의 예제프로그램을 컴파일하고 본격적인 테스트 해보도록 하자. 컴파일은 다음과 같이 하면된다. [root@localhost test]# gcc -o simple_snmp simple_snmp.c -I/usr/local/include/ucd-snmp -lsnmp -lcrypto
                       

다음은 테스트 결과이다. 중간중간에 결과에 대한 설명을 달아놓았으니 참고하기 바란다. # 인터페이스 주소가져오기
[root@localhost test]# ./simple_snmp ip.ipAddrTable.ipAddrEntry.ipAdEntAddr.192.168.100.130
value #1 is a IPADDR: 192.168.100.130

# 2번 인터페이스(eth0)에 대한 MTU사이즈 가져오기
# .2 대신에 .1을 사용할경우 1번 인터페이스(loopback)에 대한 MTU사이즈를
# 얻어올수 있다.
[root@localhost test]# ./simple_snmp interfaces.ifTable.ifEntry.ifMtu.2
value #1 is a INTEGER: 1500

# 2번 인터페이스를 통해 바깥으로 전송된 패킷의 갯수
[root@localhost test]# ./simple_snmp interfaces.ifTable.ifEntry.ifOutOctets.2
value #1 is a INTEGER: 1192344

# 시스템부팅후 경과시간  
[root@localhost test]# ./simple_snmp system.sysUpTime.0                      
value #1 is a INTEGER: 166258

# 2번 인터페이스의 속도
# 10Mbps 랜임을 알수 있다.
[root@localhost test]# ./simple_snmp interfaces.ifTable.ifEntry.ifSpeed.2
value #1 is a INTEGER: 10000000
                       

테스트를 위해서 어떤 MIB(OID)가 필요한지 잘 모르겠다면, ucd-snmp에서 제공하는 메니저 프로그램중 snmpwalk를 이용해서 정보를 요청해서 이걸 파일등으로 저장한후 참고하면 된다.  
[root@localhost test]# snmpwalk localhost public > snmp_info.dump  
                       

snmpwalk 는 GET NEXT를 이용하며 위의 경우 MIB이름을 지정하지 않았음으로 root노드를 지정했다고 간주하고 모든 MIB데이타를 긁어온다. 만약 system하위에 있는 데이타들을 요청한다면 public 다음에 system을 지정해 주면 될것이다.

만약 외부로 나가는 패킷의 양(트래픽)을 검사하길 원한다면 일정시간간격으로 "interfaces.ifTable.ifEntry.ifOutOctets.2" 정보를 요청해서 통계를 내면 된다. 이런식으로 해서 최소한의 네트워크 관리를 위한 정보수집이 가능해 진다.


--------------------------------------------------------------------------------

2.3절. 다른 예제 : 비동기 데이타 요청
우리는 2.2절에서 snmp응용 애플리케이션을 만들기 위한 기본적인 내용들에 대해서 알아보았다. 위에서 만들어진 예제프로그램은 동기형식을 가진다. 즉 요청을 보내고 응답이 있을때까지 해당영역에서 블럭킹되는데, 이러한 모델은 단지 몇개의 에이전트로 부터 데이타를 받을 경우 문제가 없지만, 여러개의 에이전트로부터 데이타를 받을 경우 문제의 소지가 있다. NMS의 특성상 다수의 에이전트를 하나의 메니저에서 관리해야하는 경우가 많음으로 동기모델로 메니저프로그램을 만드는것은 아무래도 문제가 있어보인다.

이러한 문제의 해결을 위해서 ucd-snmp는 비동기모델로 메니저를 작성할수 있도록 방법을 제공하고 있다. 언뜻 생각해도 udp를 사용하는 snmp에는 비동기모델의 어플리케이션이 더 어울릴거라고 생각할수 있다.

다음은 예제이다. 특별히 어려워보이는 건 없을것이다. 내용에 대한 설명은 주석으로 대신한다.

asynch_snmp.c




여러개의 에이전트를 등록시켜서 테스트할수 있었으면 했으나 사용할 에이전트가 한정된 관계로 하나의 에이전트만을 등록시켜서 테스트했다. 다음은 테스트 결과이다.

만약 에이전트가 실행중이지 않다면 타임아웃 에러가 발생할 것이다.

[root@localhost c_source]# ./snmp_as
23:54:48.463404 localhost: Timeout
                       

에이전트가 실행중이라면 값을 가져오게 될것이다.

[root@localhost c_source]# ./snmp_as
23:55:55.536827 localhost: system.sysUpTime.0 = Timeticks: (6401) 0:01:04.01
23:55:55.539176 localhost: interfaces.ifTable.ifEntry.ifSpeed.2 = Gauge32: 10000000
23:55:55.540540 localhost: interfaces.ifTable.ifEntry.ifOutOctets.2 = Counter32: 4236
                       

--------------------------------------------------------------------------------

3절. 결론
이상 snmp데이타를 가져오기 위한 몇가지 방안들에 대해서 알아보았다. 여기에서는 단지 GET메서드에 대한 내용만 다루고 있는데 GET NEXT는 다음번에 별도로 다루도록 할것이다.

------------
cf :  ucd-snmp, net-snmp 라이브러리가 쓰레드를 지원하지 않는다는 점이 있음.

출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • pcap 을 이용한 id,password 정보가져오기 (0)2007/05/14
  • SNMP개요및 설치,운용 (0)2007/05/11
  • SNMP응용 프로그램 제작 (0)2007/05/11
  • libpcap을 이용한 포트스캐닝 검사 (2)2007/05/11
  • 네트워크관련 정보 얻기 (0)2007/05/11
2007/05/11 10:33 2007/05/11 10:33
Posted by webdizen
Tags net-snmp, SNMP, ucd-snmp
No Trackback No Comment

Trackback URL : http://www.webdizen.net/blog/trackback/2928

Leave your greetings.

[로그인][오픈아이디란?]

Programming/Network Programming2007/05/11 10:20

libpcap을 이용한 포트스캐닝 검사

Port scanning을 검사해내는 간단한 프로그램을 작성해보도록 하겠다. 덤으로 DOS와 같은 고전적인 공격을 검사해내는 프로그램에 대한 아이디어도 얻을 수 있을 것이다.


1절. 소개
2절. JPSD 제작
2.1절. 프로그램에 대한 개략적인 기능명세
2.1.1절. Port Scanning 탐지및 PORT 통계
2.1.2절. DOS공격 탐지
2.2절. 구현 프로세스
3절. 예제
4절. 결론

--------------------------------------------------------------------------------

1절. 소개
이번에는 두번에 걸쳐 다루었던 libpcap기술을 응용해서 각 서비스 포트에 대한 네트워크 통계및 포트스캐닝을 검사하는 프로그램을 만들어 보도록 하겠다.


--------------------------------------------------------------------------------

2절. JPSD 제작
이번에 작성하고자 하는 프로그램은 JPSD라는 다소 촌스러운 이름을 가진 프로그램이다. 눈치 챘겠지만 Joinc Port Scanning Decter의 첫 글자를 따서 이름을 지었다. 다소 거부감이 느껴지더라도 그러려니 하고 넘어가주기 바란다.


--------------------------------------------------------------------------------

2.1절. 프로그램에 대한 개략적인 기능명세
2.1.1절. Port Scanning 탐지및 PORT 통계
크래킹을 하기 위해서 가장 먼저 하는 일은 목적으로 하는 서버에 대한 정보를 취득하는 작업이며, 이를 위한 가장 손쉽고 효과적인 방법은 열려있는 포트에 대한 정보를 얻는 작업이다. 이러한 포트검색을 위한 대표적인 프로그램은 namp이며 서버에 Port Scanning이 이루어졌다면 주의깊게 서버를 감시해야될 필요가 있다.

JPSD의 주요 기능은 PORT통계를 내고 이를 기초로 Port Scanning이 있었는지를 확인한다.


--------------------------------------------------------------------------------

2.1.2절. DOS공격 탐지
DOS(DDOS)공격은 공격기법중에서도 매우 고전적이고 어찌보면 고리타분한 공격일 수도 있겠지만 구현이 매우 쉬운데다가 마땅히 차단할 방법이 없기 때문에 간혹 네트워크(혹은 단일 호스트)에 매우 치명적인 영향을 주기도 한다.

2003년 1월 25일 발생한 인터넷 대란이 이러한 경우다. 원인은 SQL_Hell이라는 웜바이러스 때문인데 이 바이러스는 MSSQL의 resoultion버그를 이용해서 특정포트로 무한정 패킷을 보낸다. 또한 주위의 또다른 MSSQL서버에 침투에서 이와 동일한 일을 하게 되고 결국 전체 네트워크 시스템이 맛이가게 되었다. 전체 네트워크 시스템으로 확대되기는 했지만 결국 특정 서비스에 대해서 다량의 패킷을 보내에서 호스트를 마비시키고 덤으로 네트워크 트래픽을 증가시켜서 네트워크에 문제가 발생하는 공격이란 점에서 이번 인터넷 대란은 (좀더 지능적인)DOS 공격에 의해서 일어난 것으로 분석할 수 있다.

만약 이때 라우터나 특정 호스트에 이러한 DOS공격을 탐지할 수 있는 장치가 마련되어 있었다면 국가 전체의 네트워크 시스템이 맛가는 일은 막을 수 있었을 것이다. 기본적으로 DOS공격을 완벽하게 막는건 거의 불가능하다고 할 수 있지만 탐지는 그리 어렵지 않게 가능하다.

라우터나 혹은 패킷을 검사할 수 있는 호스트에서 패킷을 검사하고 패킷에 대한 목적지 포트번호를 가져오고 이에 대해서 일정시간 간격으로 카운팅 하기만 하면 된다. 이들 카운팅 테이터는 일정한 시간단위로 저장되어서 평균값을 유지하고 최근의 카운팅 데이터와 비교하면 특정 포트로의 변화를 감지 할 수 있게 된다. 만약 특정 포트에 대해서 평균치에 비해 갑자기 많은 카운팅이 발생한다면 이를 위험신호로 판단하여 조취를 취할 수 있을 것이다.

우리가 작성할 JPSD는 비록 포트에 대한 대략적인 통계와 이들 통계에 대한 자료를 이용하여 PORT Scanning이 이루어지고 있는지 확인하는 정도이지만, 단지 포트에 대한 통계만을 가지고서도 대략적인 DOS공격에 대한 탐지도 가능할 것이다.


--------------------------------------------------------------------------------

2.2절. 구현 프로세스
아이디어를 (제대로)구현하기 위한 프로세스는 그리 간단하지가 않다.

일단 호스트에 설치된 JPSD는 자신의 IP를 목적으로 하고 있는 모든 패킷에 대해서 검사를 해야만 한다. 이는 RAW소켓을 이용해서 검사하면 된다.

패킷을 얻었다면 IP헤더를 검사해서 목적지와 포트를 검사한다음 포트에 대한 카운팅을 실시한다. 카운팅이 되었다면 카운팅 된 숫자가 일정갯수를 초과하는지 검사해야 한다. 이때 초과여부는 두가지 방법에 의해서 검사된다. 첫번째는 절대값에 의한 검사이며, 두번째는 예전에 조사되었던 카운팅의 평균값을 구하고 그 평균값과 현재의 값을 비교하는 방법으로 두가지 방법 모두 병행해서 사용한다. 이 평균값을 구하는 것 역시 그리 간단하지가 않다. 단지 평균값보다 얼마를 초과 했느냐를 구하는게 아닌 증가율을 구해야 하기 때문이다. 또한 동일한 증가율이라고 하더라도 카운팅갯수에 따라 달라져야 한다. 예를 들어 평균 카운팅 갯수가 100000개인데 가장 최근 카운팅 갯수가 200000이라면 DOS류의 공격으로 의심할 수 있을 것이다. 그러나 10개에서 20개로 늘어났을 경우 단지 2배의 증가율을 보였다고 해서 DOS류의 공격으로 의심할 수는 없을 것이다.

게다가 이러한 정보들을 파일로 저장하고 있어야 한다. 파일로 저장된 정보라면 그래프로 이들 정보를 보여줄 수도 있을 것이다. 제대로 만들려면 이래 저래 해결해야 될 문제들이 꽤 많다.

여기에서는 이런 복잡한 것들에 대해서 신경쓰지 않을 것이다. 단지 포트에 대한 카운트를 검사하고 일정시간 후에 이를 출력해서 DOS공격인지에 대한 판단은 유저에게 맡기도록 할 것이다. 사용고 5분단위로 카운팅된 정보를 클라이언트로 전달하는 정도의 기능만 포함 시키도록 하겠다.

제대로 작동하는 DOS공격 탐지 프로그램은 개인적으로 별도의 프로젝트로 진행할 생각으로 때가 되면 공개하도록 하겠다. (그 때가 언제가 될런지는 아무도 모른다 -.-);


--------------------------------------------------------------------------------

3절. 예제
다음은 예제 프로그램이다. 언제나 처럼 코드는 깔끔, 효율, 효과적..과는 거리가 먼 그냥 돌아가는 한마디로 말해서 최소한의 기능구현에만 신경을 쓰면서 작성되었다. (한마디로 귀찮아서 대충대충 작성한 -.-);

좀더 그럴듯하게 만드는건 각자의 몫으로 남겨두도록 하겠다.

예제 : jpsd.cc

               

화면 출력을 위해서 ncurses라이브러리를 사용했다 ncurses에 대한 정보는 ncurses 프로그래밍을 참고하기 바란다. 포트 통계를 위해서 사용한 pcap라이브러리에 대한 설명은 libpcap를 이용한 프로그래밍을 참고하면 된다.

ncurses와 libpcap에 대한 이해만 가지고 있다면 주석만으로도 충분히 이해가능한 코드이다. 컴파일 방법은 다음과 같다.

# g++ -o jpsd jpsd.cc -lpthread -lpcap -lpthread
               

다음의 필자의 사이트에서 실제로 jpsd를 실행시킨 결과의 화면이다.

사용자 삽입 이미지
그림 1. jpsd의 실행화면


정말로 port scan을 검사해 낼 수 있는지의 확인을 원한다면 nmap등을 통해서 테스트 해보기 바란다.


--------------------------------------------------------------------------------

4절. 결론
이상 libpcap을 이용해서 포트통계와 포트 스캐닝을 검사해내는 프로그램을 만들어 보았다. 위의 프로그램은 매우 아이디얼한 프로그램으로 실제 그럴듯하게 작동하기 위해서는 많은 기능들이 추가되어야만 할것이다.

우선은 결과값을 네트워크를 통해서 원격지에 전송할 수 있도록 서버/클라이언트 모델로 확장시켜야 하며, 정확한 통계를 위해서 통계결과값을 파일이나 DB로 남기는 기능을 추가시켜야 한다. 또한 문제가 발생했을 경우 문제될만한 IP에 대한 목록을 출력하는 기능도 포함되어야 한다.

보여주는 것 역시 투박한 터미널 화면 보다는 GUI화면을 통해서 쉽게 결과를 확인가능 하도록 만들어 주어야 할것이다.

위에서 언급했듯이 필자는 좀더 그럴듯하게 작동하는 프로그램을 만드는 프로젝트를 진행할 계획이며, 어느정도 완성되었을 경우 공개할 예정이다.



출처  : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
"Network Programming" 카테고리의 다른 글
  • SNMP개요및 설치,운용 (0)2007/05/11
  • SNMP응용 프로그램 제작 (0)2007/05/11
  • libpcap을 이용한 포트스캐닝 검사 (2)2007/05/11
  • 네트워크관련 정보 얻기 (0)2007/05/11
  • Socket에서 완벽한 Receive처리 (0)2007/03/02
2007/05/11 10:20 2007/05/11 10:20
Posted by webdizen
Tags libpcap, 포트스캐닝
No Trackback 2 Comments

Trackback URL : http://www.webdizen.net/blog/trackback/2926

Leave your greetings.

  1. 비밀방문자

    관리자만 볼 수 있는 댓글입니다.

    2008/11/06 16:30 [ Permalink : Modify/Delete : Reply ]
    • webdizen

      안녕하세요. 정진님.
      제가 도와드리고 싶지만 세그멘테이션 오류가 발생하는 경우가 한두가만 있는 것이 아니라서 파악하기 어렵네요.
      컴파일 환경만 알고서는 오류의 원인을 파악하기 어렵습니다.

      2008/11/10 07:08 [ Permalink : Modify/Delete ]
[로그인][오픈아이디란?]

«Prev  1 2  Next»

RSS HanRSS
Blog Image
webdizen
이 곳은 컴퓨터에 대해 연구하고, 공유하고, 소통하기 위한 연구실입니다. 개인적으로는 OLAP, Data Mining, Semantic Web, Data Modeling에 대해서 연구하고 있습니다.

Categories

전체 (2998)
Webdizen (134)
Life (6)
Diary (16)
Blog (9)
IDEA (1)
Travel (10)
Book (14)
Photo (7)
Movie (7)
Music (13)
Leisure Sports (10)
Funny (5)
Hardware (119)
Software (120)
Windows (5)
Unix & Linux (119)
Installation (4)
Kernel (10)
System (34)
Develop (22)
X-Window (0)
Applicaton (31)
Security (4)
Framework (2)
Hadoop (2)
Programming (805)
Algorithm & Data Structure (1)
Assembly (38)
UNIX/Linux C (95)
C++ (128)
STL (4)
Java (38)
Win32 API (92)
ATL/COM (44)
MFC (151)
.NET (26)
WCF/WPF (4)
C# (28)
Network Programming (17)
Database Programming (12)
OpenGL / DirectX (13)
Multimedia Programming (0)
Game Programming (21)
Parallel Distributed Progra... (0)
Reverse Engineering (0)
Debugging (9)
Python (1)
Ruby (1)
Ruby on Rails (1)
QT (4)
GTK (0)
JSP (0)
PHP (6)
ASP.NET (6)
ASP (3)
Development (28)
Useful Library (2)
Data Modeling (0)
Database (105)
Oracle (4)
MSSQL (41)
MySQL (2)
Data Warehouse (2)
Data Mining (3)
Network (66)
Web (78)
DHTML (4)
XHTML (1)
Javascript (1)
CSS (1)
AJAX (9)
XML (11)
Flex (1)
Silverlight (3)
Security (91)
DoS (1)
Kernel (10)
Scanning (3)
Sniffing (0)
Spoofing (4)
Overflow (28)
Web (11)
Shell (10)
Format String (14)
Window (2)
Embedded (70)
Multimedia (27)
Mobile (14)
Graphic (24)
Management (633)
Knowledge (581)
Hadoop (0)

Notice

  • 메타 블로그 사이트에 등록
  • 새해 맞이 블로그의 변화
  • 블로그 명칭 변경
  • 도메인(www.webdizen.net) 구...
  • TEXTCUBE 1.6.1로 업그레이드...

Tags

  • 관찰
  • 김중태
  • 경청
  • 빠에야
  • hardware interrupt
  • 나눔고딕
  • Redirection
  • PE
  • RegOpenKey
  • WORLDCOMP
  • Web 2.0
  • 후처리
  • 프로필러
  • SetFocus
  • DirectDraw
  • CEdit
  • 디자인 패턴
  • 구본관
  • Refactoring
  • 데이터 웨어하우스

Recent Articles

  • ASCII Code의 CRLF 제거 방법.
  • Hadoop 에서 c++ API 이용시....
  • Ubuntu Linux에서 Hadoop 구....
  • 내 심장을 한껏 뛰게한 "국가....
  • 스타 스키마 데이터베이스 설....

Recent Comments

  • ■ 온라인카지노 ▶ http://L....
    asdf 11/21
  • 그리고 혹시 해외여행자보험....
    kim 11/05
  • ★★실제 바다게임장과 똑같....
    asdf 11/04
  • sbsyama.co.to← 짱5000만당....
    asdf 11/04
  • ♡KicaZ??o(???) 바카라사....
    fdsf3fass 11/03

Recent Trackbacks

  • 파일 열기/저장하기 CFileDialog.
    은마군의 나태블록 02/11
  • World IT Show 2008.
    상우 :: Oranzie's BLOG 2008
  • cvs서버 설치하기.
    3인3색 2008
  • 속속 공개되는 Google Chart....
    PHP와 Web 2.0 2007
  • 마방진을 구하는 프로그램.
    Oranzie's BLOG 3 2007

Archive

  • 2009/09 (3)
  • 2009/08 (1)
  • 2009/03 (1)
  • 2009/02 (9)
  • 2009/01 (13)

Calendar

«   2009/11   »
일 월 화 수 목 금 토
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

Bookmarks

    • Administration
      • IIS.NET
      • NTFAQ
      • OS의 모든 것
      • 리눅스포털
    • Database
      • SQL Server Central
      • SQL Team
    • Development
      • .NET Heaven
      • ASP Alliance
      • ASP.NET 2.0
      • Bullog.net
      • C# Corner
      • C++ (C PlusPlus.com)
      • C++ Reference
      • CodeGuru
      • CodePlex
      • DebugLab
      • Dev Articles
      • Devpia
      • DotNet Junkies
      • DotNet Zone
      • Driver Online
      • GOSU.NET
      • HOONS 닷넷
      • Joinc 팀블로그
      • KOSR
      • MSDN Home Page
      • OSR Online
      • Sky.ph - 개발자 커뮤니...
      • TAEYO.NET
      • The Code Project
      • WindowsClient.net
      • 김상욱의 개발자 Side
      • 조인시 위키
    • Human Networks
      • belief21c's e-space
      • I think I can
      • Invisible Rover's Blog :D
      • Rodman®
      • ■ Feel So Good~! ■
      • 까만 나비
      • 나를 가꾸는 시간.
      • 나만의 즐거움~~!
      • 단녕
      • 상우 :: Oranzie's BLOG
    • Information Technology
      • Microsoft TechNet
      • 지디넷코리아 - 글로벌...
    • Security
      • FoundStone
      • milw0rm
      • NewOrder
      • OpenRCE
      • Phrack.org
      • Reverse Engineering b1...
      • Reverse Engineering Team
      • RootKit
      • SecurityFocus
      • SecurityXploded by Nag...
      • Wow Hacker
      • Zone-H
Textcube
Louice Studio Inc.
Powered by Textcube. Original designed by Tistory.