개발 및 디버깅에 유용한 툴 몇가지를 간단히 소개하겠습니다. 아시는 분도 있지만 참조하세요! 

[예제 프로그램] 

# vi hello.c 
-------------------------------------------------------------------------------------------------------------------------------------------------------- 
#include 

int main() 

printf(Hello, World!n); 
getchar(); 
return 0; 

-------------------------------------------------------------------------------------------------------------------------------------------------------- 
# gcc -o hello hello.c 
# ./hello 
Hello, World! 


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

nm : list symbols from object files 

# nm hello 

08048374 t Letext 
080494d4 ? _DYNAMIC 
080495ac ? _GLOBAL_OFFSET_TABLE_ 
080484b0 R _IO_stdin_used 
080495a0 ? __CTOR_END__ 
0804959c ? __CTOR_LIST__ 
080495a8 ? __DTOR_END__ 
080495a4 ? __DTOR_LIST__ 
080494d0 ? __EH_FRAME_BEGIN__ 
080494d0 ? __FRAME_END__ 
080495d0 A __bss_start 
080494c4 D __data_start 
w __deregister_frame_info@@GLIBC_2.0 
08048460 t __do_global_ctors_aux 
080483a0 t __do_global_dtors_aux 
w __gmon_start__ 
U __libc_start_main@@GLIBC_2.0 
w __register_frame_info@@GLIBC_2.0 
080495d0 A _edata 
080495e8 A _end 
08048490 T _fini 
080484ac R _fp_hw 
080482c0 T _init 
08048350 T _start 
08048374 t call_gmon_start 
080494cc d completed.4 
080494c4 W data_start 
080483f0 t fini_dummy 
080494d0 d force_to_data 
080494d0 d force_to_data 
080483f8 t frame_dummy 
08048374 t gcc2_compiled. 
080483a0 t gcc2_compiled. 
08048460 t gcc2_compiled. 
08048490 t gcc2_compiled. 
08048430 t gcc2_compiled. 
U getchar@@GLIBC_2.0 <=== glibc 2.0버전 라이브러이의 getchar함수를 사용하는 것을 알 수 있음 
0804841c t init_dummy 
08048484 t init_dummy 
08048430 T main 
080495d0 b object.11 
080494c8 d p.3 
U printf@@GLIBC_2.0 <=== glibc 2.0버전 라이브러이의 printf함수를 사용하는 것을 알 수 있음 
------------------------------------------------------------------------------------------------------------------------------------------------------- 

ldd : print shared library dependencies 

# ldd hello 
libc.so.6 => /lib/libc.so.6 (0x40029000) 
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000) 
-------------------------------------------------------------------------------------------------------------------------------------------------------- 

lsof : list open files 
현재 실행중인 프로세스가 open한 모든 파일 목록을 출력해줍니다. 
유닉스나 리눅스는 모든 것을 파일로 다루므로 일반 파일, 장치 디바이스, 디렉토리, Socket, FIFO, PIPE, 공유 라이브러리, ... 을 모두 출력해줍니다. 
자기가 만들거나 다른 사람이 만든 어플리케이션이 어떤 장치 디바이스를 사용한다거나, 현재 open한 Socket의 개수를 알아보거나, 참조하는 공유 라이브러리가 무엇인지 등의 정보를 알 수 있습니다. 
배포 패키지를 개발할때 참조하는 공유 라이브러리 정보를 알수 있으므로 유용하게 쓰일 수 있습니다. 

[터미널 1] 
# ./hello 
 <=== 키 입력을 대기한다. 

[터미널 2] 
# lsof | grep hello 
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME 
hello 11856 root cwd DIR 3,1 704 57679 /usr/src <=== cwd : 현재 작업 디렉토리 
hello 11856 root rtd DIR 3,1 576 2 / 
hello 11856 root txt REG 3,1 13331 3216 /usr/src/hello 
hello 11856 root mem REG 3,1 435016 205533 /lib/ld-2.2.5.so <=== mem : 메모리 맵 파일, 사용중인 공유 라이브러리 
hello 11856 root mem REG 3,1 5029105 205536 /lib/libc-2.2.5.so <=== mem : 메모리 맵 파일, 사용중인 공유 라이브러리 
hello 11856 root 0u CHR 136,7 9 /dev/pts/7 <=== 0u(stdin), /dev/pts/7 : 사용중인 터미널 디바이스, 
프로그램이 실행하면 세개의 화일인 stdin, stdout, stderr 자동생성한다. 
hello 11856 root 1u CHR 136,7 9 /dev/pts/7 <=== 1u(stdout), 
hello 11856 root 2u CHR 136,7 9 /dev/pts/7 <=== 2u(stderr) 

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

결과가 나왔으면 [터미널 1]으로 이동하여 엔터키를 쳐서 hello 프로그램을 종료시킨다. 


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

file : determine file type 

# file hello 
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped 

ELF 32-bit LSB executable : 실행파일형식 
Intel 80386 : Intel 80386 어셈블리어로 되어있음 
dynamically linked (uses shared libs) : 동적 라이브러리가 연결 가능하다. 
not stripped : 심볼 정보가 남아있는 경우 

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

strip : Discard symbols from object files. 

# strip hello 
# file hello 
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), stripped <=== 심볼 정보가 삭제됐음을 알 수 있다. 
--------------------------------------------------------------------------------------------------------------------------------------------------------

출처 - http://k.daum.net/qna/view.html?qid=00gVK

'System > Linux' 카테고리의 다른 글

centos - python 2.6.x 설치  (1) 2012.07.13
CentOS - RPMforge EPEL Remi 저장소 설치  (0) 2012.07.13
Linux - iptables를 이용한 포트포워딩  (1) 2012.05.26
/etc/shadow  (0) 2012.05.25
BIND 설치  (0) 2012.05.16
Posted by linuxism
,

libpcap

System/Common 2012. 7. 9. 22:42



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()

int pcap_lookupnet(char *device, bpf_u_int32 *netp, 
               bpf_u_int32 *maskp, char *errbuf)
				

네트웍 디바이스에 대한 네트웍 및 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

int pcap_datalink(pcap_t *p)
				
link layer 타입을 되돌려준다(DLT_EN10MB 과 같은)


3.1.4절. 예제

예제 : pcap.c

#include <stdio.h>
#include <stdlib.h>
#include <pcap.h>     //libpcap 헤더 포험
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char **argv)
{
    char *dev;    // 사용중인 네트웍 디바이스 이름 
    char *net;    // 네트웍 어드레스 
    char *mask;   // 네트웍 mask 어드레스 
    int ret;      //  
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 netp;  // ip 
    bpf_u_int32 maskp; // submet mask
    struct in_addr addr;

    // 네트웍 디바이스 이름을 얻어온다. 
    dev = pcap_lookupdev(errbuf);

    // 에러가 발생했을경우 
    if(dev == NULL)
    {
        printf("%s\n",errbuf);
        exit(1);
    }

    // 네트웍 디바이스 이름 출력 
    printf("DEV: %s\n",dev);

    // 네트웍 디바이스 이름 dev 에 대한 
    // mask, ip 정보 얻어오기   
    ret = pcap_lookupnet(dev,&netp,&maskp,errbuf);

    if(ret == -1)
    {
        printf("%s\n",errbuf);
        exit(1);
    }

    // 네트웍 어드레스를 점박이 3형제 스타일로
    addr.s_addr = netp;
    net = inet_ntoa(addr);

    if(net == NULL)
    {
        perror("inet_ntoa");
        exit(1);
    }

    printf("NET: %s\n",net);

    // 마찬가지로 mask 어드레스를 점박이 3형제 스타일로
    addr.s_addr = maskp;
    mask = inet_ntoa(addr);
  
    if(mask == NULL)
    {
        perror("inet_ntoa");
        exit(1);
    }
  
    printf("MASK: %s\n",mask);
    return 0;
}
				
다음은 컴파일 방법이다. 컴파일 환경은 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

pcap_t *pcap_open_live(char *device, int snaplen,
           int promisc, int to_ms, char *ebuf)
				
첫번째 인자로 주어지는 네트웍 디바이스 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

	
pcap_t *pcap_open_offline(char *fname, char *ebuf)
				
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 구조체정보이다.

tcp 헤더 구조체

struct tcphdr
  {
    u_int16_t th_sport;     /* source port */
    u_int16_t th_dport;     /* destination port */
    tcp_seq th_seq;     /* sequence number */
    tcp_seq th_ack;     /* acknowledgement number */
#  if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int8_t th_x2:4;       /* (unused) */
    u_int8_t th_off:4;      /* data offset */
#  endif
#  if __BYTE_ORDER == __BIG_ENDIAN
    u_int8_t th_off:4;      /* data offset */
    u_int8_t th_x2:4;       /* (unused) */
#  endif
    u_int8_t th_flags;
#  define TH_FIN    0x01
#  define TH_SYN    0x02
#  define TH_RST    0x04
#  define TH_PUSH   0x08
#  define TH_ACK    0x10
#  define TH_URG    0x20
    u_int16_t th_win;       /* window */
    u_int16_t th_sum;       /* checksum */
    u_int16_t th_urp;       /* urgent pointer */
};

				

IP 헤더 구조체

struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;       /* header length */
    unsigned int ip_v:4;        /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;        /* version */
    unsigned int ip_hl:4;       /* header length */
#endif
    u_int8_t ip_tos;            /* type of service */
    u_short ip_len;         /* total length */
    u_short ip_id;          /* identification */
    u_short ip_off;         /* fragment offset field */
#define IP_RF 0x8000            /* reserved fragment flag */
#define IP_DF 0x4000            /* dont fragment flag */
#define IP_MF 0x2000            /* more fragments flag */
#define IP_OFFMASK 0x1fff       /* mask for fragmenting bits */
    u_int8_t ip_ttl;            /* time to live */
    u_int8_t ip_p;          /* protocol */
    u_short ip_sum;         /* checksum */
    struct in_addr ip_src, ip_dst;  /* source and dest address */
  };
				

ETHERNET 헤더 구조체

struct ethhdr
{
    unsigned char   h_dest[ETH_ALEN];   /* destination eth addr */
    unsigned char   h_source[ETH_ALEN]; /* source ether addr    */
    unsigned short  h_proto;            /* packet type ID field */
};
				

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

u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
				
다음 패킷에 대한 포인터를 리턴한다.

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


3.3.3절. pcap_loop

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

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


3.3.4절. pcap_dispatch

int pcap_dispatch(pcap_t *p, int cnt,
         pcap_handler callback, u_char *user)
				
pcap_loop 와 거의 비슷하다.


3.4절. 패킷 필터링 관련 API

3.4.1절. pcap_compile

int pcap_compile(pcap_t *p, struct bpf_program *fp,
          char *str, int optimize, bpf_u_int32 netmask)
				
들어오는 패킷을 필터링 해서 받아들이기 위해서 사용한다. 예를 들어 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

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
				
pcap_compile 을 통해서 지정된 필터를 적용시키기 위해서 사용되며, 앞으로 들어오는 패킷에 대해서는 이 필터룰에 의해서 필터링 된다.


4절. 예제 코드

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

예제 : pcap_test.c

#include <sys/time.h>
#include <netinet/in.h>
#include <net/ethernet.h>
#include <pcap/pcap.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>

#define PROMISCUOUS 1
#define NONPROMISCUOUS 0

// IP 헤더 구조체
struct ip *iph;

// TCP 헤더 구조체
struct tcphdr *tcph;

// 패킷을 받아들일경우 이 함수를 호출한다.  
// packet 가 받아들인 패킷이다.
void callback(u_char *useless, const struct pcap_pkthdr *pkthdr, 
                const u_char *packet)
{
    static int count = 1;
    struct ether_header *ep;
    unsigned short ether_type;    
    int chcnt =0;
    int length=pkthdr->len;

    // 이더넷 헤더를 가져온다. 
    ep = (struct ether_header *)packet;

    // IP 헤더를 가져오기 위해서 
    // 이더넷 헤더 크기만큼 offset 한다.   
    packet += sizeof(struct ether_header);

    // 프로토콜 타입을 알아낸다. 
    ether_type = ntohs(ep->ether_type);

    // 만약 IP 패킷이라면 
    if (ether_type == ETHERTYPE_IP)
    {
        // IP 헤더에서 데이타 정보를 출력한다.  
        iph = (struct ip *)packet;
        printf("IP 패킷\n");
        printf("Version     : %d\n", iph->ip_v);
        printf("Header Len  : %d\n", iph->ip_hl);
        printf("Ident       : %d\n", ntohs(iph->ip_id));
        printf("TTL         : %d\n", iph->ip_ttl); 
        printf("Src Address : %s\n", inet_ntoa(iph->ip_src));
        printf("Dst Address : %s\n", inet_ntoa(iph->ip_dst));

        // 만약 TCP 데이타 라면
        // TCP 정보를 출력한다. 
        if (iph->ip_p == IPPROTO_TCP)
        {
            tcph = (struct tcp *)(packet + iph->ip_hl * 4);
            printf("Src Port : %d\n" , ntohs(tcph->source));
            printf("Dst Port : %d\n" , ntohs(tcph->dest));
        }

        // Packet 데이타 를 출력한다. 
        // IP 헤더 부터 출력한다.  
        while(length--)
        {
            printf("%02x", *(packet++)); 
            if ((++chcnt % 16) == 0) 
                printf("\n");
        }
    }
    // IP 패킷이 아니라면 
    else
    {
        printf("NONE IP 패킷\n");
    }
    printf("\n\n");
}    

int main(int argc, char **argv)
{
    char *dev;
    char *net;
    char *mask;

    bpf_u_int32 netp;
    bpf_u_int32 maskp;
    char errbuf[PCAP_ERRBUF_SIZE];
    int ret;
    struct pcap_pkthdr hdr;
    struct in_addr net_addr, mask_addr;
    struct ether_header *eptr;
    const u_char *packet;

    struct bpf_program fp;     

    pcap_t *pcd;  // packet capture descriptor

    // 사용중인 디바이스 이름을 얻어온다. 
    dev = pcap_lookupdev(errbuf);
    if (dev == NULL)
    {
        printf("%s\n", errbuf);
        exit(1);
    }
    printf("DEV : %s\n", dev);

    // 디바이스 이름에 대한 네트웍/마스크 정보를 얻어온다. 
    ret = pcap_lookupnet(dev, &netp, &maskp, errbuf);
    if (ret == -1)
    {
        printf("%s\n", errbuf);
        exit(1);
    }

    // 네트웍/마스트 정보를 점박이 3형제 스타일로 변경한다. 
    net_addr.s_addr = netp;
    net = inet_ntoa(net_addr);
    printf("NET : %s\n", net);

    mask_addr.s_addr = maskp;
    mask = inet_ntoa(mask_addr);
    printf("MSK : %s\n", mask);
    printf("=======================\n");

    // 디바이스 dev 에 대한 packet capture 
    // descriptor 를얻어온다.   
    pcd = pcap_open_live(dev, BUFSIZ,  NONPROMISCUOUS, -1, errbuf);
    if (pcd == NULL)
    {
        printf("%s\n", errbuf);
        exit(1);
    }    

    // 컴파일 옵션을 준다.
    if (pcap_compile(pcd, &fp, argv[2], 0, netp) == -1)
    {
        printf("compile error\n");    
        exit(1);
    }
    // 컴파일 옵션대로 패킷필터 룰을 세팅한다. 
    if (pcap_setfilter(pcd, &fp) == -1)
    {
        printf("setfilter error\n");
        exit(0);    
    }

    // 지정된 횟수만큼 패킷캡쳐를 한다. 
    // pcap_setfilter 을 통과한 패킷이 들어올경우 
    // callback 함수를 호출하도록 한다. 
    pcap_loop(pcd, atoi(argv[1]), callback, NULL);
}
		

컴파일 방법은 아래와 같다.

[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://blog.daum.net/njaewon/33

'System > Common' 카테고리의 다른 글

ssh 일반 계정 접속 제한  (0) 2012.07.16
초보자를 위한 exim4 설치하기  (0) 2012.07.10
DNS - named 설정 점검  (0) 2012.05.14
Cloud Computing  (0) 2012.04.27
대용량 데이터 분석 기술 (Big Data Analytics)  (0) 2012.04.26
Posted by linuxism
,


세션생성,삭제하는 법

actionContext로 구하는 법과 SessionAware로 구하는 법,
httpServletRequest로 구하는 법을 정리합니다.

1. actionContext로 구하는 법
<세션생성>

      import com.opensymphony.xwork.ActionContext;

      Map session = ActionContext.getContext().getSession();

      session.put("session_id", id);

<세션삭제>
     Map session = ActionContext.getContext().getSession();
     session.remove("session_id");


2. SessionAware 인터페이스를 구현하면 session을 얻을수 있다

<세션생성>
import com.opensymphony.xwork.ActionSupport;
import org.apache.webwork.interceptor.SessionAware;
import java.util.Map;
public class SampleForm extends ActionSupport implements SessionAware{

  private Map session;
  public String execute() throws Exception {
      session.put("session-userid", id);
  }
}

3. HttpServletRequest로 session 생성
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.opensymphony.webwork.ServletActionContext;
import com.opensymphony.xwork.ActionContext;
import com.opensymphony.xwork.ActionSupport;
public class RequestSessionTestAction extends ActionSupport {
 
 private String id = null;
 
 public String execute() throws Exception {
  ActionContext ctx = ActionContext.getContext();
  HttpServletRequest request = (HttpServletRequest)ctx.get(ServletActionContext.HTTP_REQUEST);
  HttpSession session = request.getSession(false);
  session.setAttribute("session-userid", id);
 
  return SUCCESS;
 }

}


출처 - http://hmgirl.tistory.com/35


===================================================================================

 Session 생성

 HttpServlteRequest 객체의 getSession()메소드를 이용하여 Session객체를 획득할 수 있다.

▶ HttpSession getSession(boolean create)
getSession(true)일 경우 해당 Session이 존재한다면 그 Session을 Return하고, Session이 존재하지 않는다면 새로운 Session을 생성한다.
getSession(false)일 경우 해당 Session이 존재한다면 그 Session을 Return하지만, Session이 존재하지 않는다면 null을 Return한다.

▶ HttpSession getSession()
getSession(true)와 동일함

 

public class LoginAction extends Action {
 public ActionForward execute( HttpServletRequest request, HttpServletResponse response)throws Exception{
  
    // 세션 생성 부분 session 이란 이름으로 세션 생성.
  HttpSession session = request.getSession(true); 
  
  
 }

}


출처 - http://blog.naver.com/PostView.nhn?blogId=jkleehi&logNo=10034158477&redirect=Dlog&widgetTypeCall=true


===================================================================================


모든 세션을 가져오기

java 구문으로서 .java .jsp둘다 사용가능.

Enumeration 로 모든 세션의 이름을 받아 뿌려줬다.

   Enumeration se = session.getAttributeNames();
  
  while(se.hasMoreElements()){
   String getse = se.nextElement()+"";
   System.out.println("@@@@@@@ session : "+getse+" : "+session.getAttribute(getse));
  }


출처 - 

===================================================================================


흔히 invalidate()를 사용하면 세션객체가 메모리에서 사라지는 것으로 오해할 수 있다.

아마도 "소멸" 이라는 단어가 주는 느낌 때문에 그럴수도 있다.

하지만 자바의 기초를 되짚어 다시한번 생각해보자

JAVA라는 언어는 프로그래머가 메모리 관리를 주도할 수 없도록 가비지 컬렉션을 제공하지 않는가?

그렇다면 invalidate()는 세션객체 자체를 소멸시키는것이 아니라, 세션의 기능을 중단시키고

무효화 시키는것이라고 표현해도 될 것이다.

아래의 예제1을 보면 invalidate()메서드 수행후에도 세션아이디는 여전히 출력이 되기 때문이다.

[예제1]

<%@ page session="true" language="java" contentType="text/html; charset=EUC-KR"%>

<%
String id=session.getId();
out.print("생성된 세션 아이디는"+id+"<br>");
session.invalidate(); // 세션 무효화 시킴 !!
out.print("invalidate() 적용후에도 "+session.getId()+"<br>"); // invalidate() 실행 후에도 ID는 여전히 출력된다

                                                                                    // 즉 객체가 아직 소멸하지 않았다고 보아야 한다.
%>

 

소멸되지 않은 객체이긴 하지만 getSession(false)메서드에 의해 참조되어 질수도 없다 .

아래의 예제를 보자

[예제2]

<%@ page session="true" language="java" contentType="text/html; charset=EUC-KR"%>

<%
String id=session.getId();
out.print("생성된 세션 아이디는"+id+"<br>");
session.invalidate(); // 세션 무효화 시킴 !!
out.print("invalidate() 적용후에도 "+session.getId()+"<br>");
out.print(request.getSession(false).getId());

%>

request.getSession(false) 메서드는 기존에 존재하는 세션을 리턴받는 메서드인데,

이미 무효화된 세션객체를 리턴받으려고 하니 에러가 나는 것이다.

따라서 여기서 혼란을 일으킬수 있으므로 다음과 같이 정리하고 가자

invalidate()메서드는 객체를 메모리에서 삭제할수는 없지만 객체를 무효화 시켜버린다!!

따라서 참조당할 수 없으며, getId() 메서드이외에는 메서드호출 또한 불가하다.

 

메서드 호출에 대한 아래의 실험을 살펴보자

[ 첫번째 예제1 : 올바르게 실행 ]

<%@ page session="true" language="java" contentType="text/html; charset=EUC-KR"%>

<%
String id=session.getId();
out.print("생성된 세션 아이디는"+id);

session.setAttribute("memberID","zino"); // memberID 라는 변수에 zino라는 값을 부여하였다.
String memberID = (String)session.getAttribute("memberID");

out.print("세션에 부여된 memberID 변수값은 "+memberID+"입니다."); // 세션에 부여된 값을 가져올 수 있다.

%>

 

[ 두번째 예제1 : 에러 발생]

<%@ page session="true" language="java" contentType="text/html; charset=EUC-KR"%>

<%
String id=session.getId();
out.print("생성된 세션 아이디는"+id);

session.invalidate(); // 세션 무효화 시킴 !!  아랫줄 부터는 에러 발생 !!!!

session.setAttribute("memberID","zino"); // memberID 라는 변수에 zino라는 값을 부여하였다.
String memberID = (String)session.getAttribute("memberID");

out.print("세션에 부여된 memberID 변수값은 "+memberID+"입니다."); // 세션에 부여된 값을 가져올 수 있다.

%>

 

두번째 예제에서 확인했듯이, 무효화 선언 이후엔 이 세션 객체의 메서드를 더 이상 사용할 수 없다.

즉 세션객체가 역할을 상실한 상태를 일컬어 세션소멸이라고 한다.

개인적으로는 세션소멸보다는 "세션무효화"가 더 나을것 같다.

아마 그래서 메서드명도 invalid (효력이 없는) 으로 정했을지도 모른다는 생각이 든다 ^^


출처 - http://blog.naver.com/PostView.nhn?blogId=zino1187&logNo=110025698379&categoryNo=17&viewDate=&currentPage=1&listtype=0


===================================================================================


Staless 프로토콜

  • 세션에 대해 알아보기전에 우리는 HTTP의 프로토콜 특징에 대해 알아보고자 합니다.
  • HTTP 프로토콜을 기본적으로 클라이언트의 요청(request), 응답(response)로 구현되어 있습니다.
  • 이 말은, 서버에 요청을 하고 응답을 받으면 서버와의 통신이 끊기게 되고, 서버에는 클라이언트의 어떠한 정보도 유지하지 않는다는 뜻입니다.
  • 이것을 HTTP의 Staless특성인 즉, 비연결형 프로토콜이라고 합니다.
    • 장점
      • 프로토콜을 직관적으로 이해하기 쉽고, 구현이 단순합니다.
      • 클라이언트의 요청에 대한 처리 결과를 응답으로 전송하고 나면 접속이 종료되기 때문에, 서버측 네트워크 자원의 효율 성이 증가합니다. 즉, 더 많은 클라이언트 요청을 처리 할 수 있습니다.
    • 단점
      • 각 클라이언트 요청마다 새로운 접속이 이루어지기 때문에 서버측의 네트워크 자원의 낭비가 적지만, 새로운 접속을 맺기 위해 발생하는 오버헤드 즉, 수행속도의 감소가 지속적으로 발생할 수 있습니다.
      • 또한, 동일한 클라이언트가 접속하더라도, 이전의 정보가 남아있지 않아서 클라이언트의 정보에 대한 비교가 불가능 합니다.

 

세션 (HTTP Session)

 ▶가상의 접속 상태를 유지하기 위한 메카니즘입니다.

  • HTTP세션이 종료되는 시점은, 클라이언트 세션 아이디가 무효화 되는 시점입니다. 이 시점은 로그 아웃 등을 할 때 종료된다고 보면 됩니다.
  • 웹에서 세션을 사용하고 있다면, 반드시 세션을 종료하는(로그아웃) 방법도 제공을 해주어야 합니다.
  • 일정 시간 동안 동일한 클라이언트로부터 요청이 발생하지 않는다면, 이 또한 세션이 종료되도록 할 수 있습니다. web.xml에 설정된 경우 <session-config> 의 <session-timeout>설정을 통해 시간 설정이 가능합니다.
    • 이미지를 클릭하시면 원본크기로 보실수 있습니다.

    • 위의 설정대로 한다면, Session  은 30분후에 종료가 되겠다는 뜻이구요, 아파치 웹 서버를 따로 구성했을 경우에는 아파치 웹 서버의 WEB.XML을 수정해야 합니다.

쿠키

 ▶쿠키라는 것은 클라이언트의 상태 정보를 서버로 전송하기 위해 사용할 수 있는 방법 중의 하나로서 클라이언트에 저장되는 단순한 텍스트 입니다.

  • 쿠키는 브라우저가 종료되더라도 정보가 사라지지 않습니다.
  • 쿠키는 사용자가 컴퓨터를 켜든 끄든 하드디스크에 (상당기간) 저장되어 있습니다
  • 왜? PC에 저장하는 걸까요?
    • 그것은 HTTP 프로토콜이 'stateless' 프로토콜이기 때문입니다. 웹 브라우저가 웹 서버에 접속을 해서 어떤 문서나 파일을 요청하면 웹 서버는 요청 받은 내용을 보내준 다음 접속을 끊습니다.
       
      즉, 접속을 한 '상태(state)'가 지속되지 않고 요청된 것만 처리한 뒤 연결을 끊는 거죠. 그러므로 웹 서버는 일단 요청된 내용들을 클라이언트에 보내고 나면 그 뒤 사용자가 접속을 하고 있는지 어떤지 알 수가 없습니다.
    • 나아가, 예전에 접속했던 클라이언트가 또 접속을 한 것인지 아닌지 등은 더더욱 알 수 없습니다. 그런데 웹 사이트를 운용하는 측에서는 어떤 사용자가 다시 방문을 했는지 와 같은 정보가 절실히 필요했고 바로 이런 점을 해결하기 위해, 즉 stateless한 http의 특징을 커버하기 위해 등장한 아이디어가 쿠키(Cookie)입니다.
    • 쿠키의 아이디어는 간단합니다. 접속한 클라이언트의 하드디스크에 적당한 정보를 저장해 둠으로써 또 그 클라이언트가 접속한 경우 언제든지 하드디스크에 저장된 정보를 읽어 들여서 그 사용자를 인식할 수 있는 것입니다. '
    • 상태'에 관한 점검을 언제든지 할 수 있는 것이죠.
    • 쿠키에 저장되는 내용은 천차만별입니다. 간단하게는, 사용자가 어떤 페이지를 읽었고, 로그인 아이디가 뭐고, 이 메일 주소가 뭐고 등을 기록할 수도 있고, 사용자가 어떤 물품을 주문했는지, ip 주소가 뭐고, 어떤 사이트를 거쳐서 우리 사이트로 왔는지, 또는 서버에서 각 클라이언트를 식별할 특별한 정보를 기록하는 등, 거의 모든 형태의 정보를 저장할 수 있습니다.
    • 사용자 처지에서는 사실 기분 나쁠 수 있습니다. 나도 모르게 나의 행동이 하나하나 기록되어 '파일'로 저장되고 있고, 그 파일이 다른 곳도 아닌 '내' 컴퓨터에 나도 모르게 저장된다는 것은 별로 좋은 느낌은 아니죠.
    • 하지만, 쿠키 파일은 사용자가 컴퓨터를 끄든 켜든 하드디스크에 (상당 기간) 저장되어 있기 때문에, 언제든지 사용자가 다시 어떤 웹 사이트에 접속하면 쿠키에 저장해 놓은 정보를 읽어 들여서 여러 형태의 '맞춤화된' 서비스를 제공할 수 있습니다. 이를 테면, 로그인을 한 번만 하면 그 다음부터 안 해도 된다든지, 어떤 페이지를 "몇 번 보셨군요" 라고 알려준다든지 등이 가능합니다.
  • JSP에서 쿠키 설정하는 방법
    1. 헤더에 직접 지정하는 방법
      • HTTP 헤더를 이용한 쿠키 설정
      • Set-Cookie : name = value; expires = date; domain = serverDomain;
      • path = serverPath; secure(HTTPS)
    2. 쿠키를 추상화 시켜놓은 서블릿 API를 이용하는 방법
      • 이것은 보다 객체 지향적인 방법으로 클라이언트와 서버간의 쿠키 데이터 전송을 위해 Set-Cookie와 쿠키 헤더를 추상화한 javax.servlet.http.Cookie 클래스를 사용합니다.

 

 JSP에서의 세션 관리 

▶HttpSession과 세션 관리

  • HttpSession은 서블릿/JSP에서 세션을 유지하기 위해 필요한 일련의 작업을 단순화 혹은 추상화시켜 놓은 API라고 생각하면 될 것입니다.
  • 세션을 유지할 때는 다음의 방법을 사용합니다.
    1. 최초의 클라이언트에서 요청할 때 세션 유지를 위해 HttpSession 객체를 생성하고 고유한 세션 아이디를 생성합니다.
      • HttpSession session = request.getSession();
      • getSession()메소드는 create가 true일 경우 클라이언트 요청에 대해 기존에 생성된 객체가 존재할 때는 해당 객체를 리턴하고, 그렇지 않으면 새로운 객체를 생성하여 리턴합니다.
      • create가 false 인 경우에 요청에 대해 생성된 HttpSession객체가 존재할때 이를 리턴하고, 그렇지 않으면 null 을 리턴합니다.
      • 일반적으로 getSession()메소드는 getSession(true)과 동일하다고 보면 도비니다.
    2. HttpSession 객체 생성시 자동으로 생성된 세션 아이디를 이후의 클라이언트 요청부터 요청에 포함하여 전송합니다.
    3. 세션이 시작된 이후의 모든 요청이 들어올 때 마다 서버가 클라이언트에 부여한 세션 아이디를 통해 세션을 유지하도록 합니다.
  • [세션 테스트]

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

이미지를 클릭하시면 원본크기로 보실수 있습니다.

차암 쉽쬬잉~~~~~~~~~♬

★만약, HttpSession을 사용하지 않고, page 속성에서 session = true로 했을 경우에는 JSP 내장  Session객체를 사용함을 의미합니다. 이 때에 HttpSession session 할 경우에는 동일한 session 이름의 객체가 생성되므로 HttpSession으로 만든 session에 오류가 날 수 있습니다. 이 때에는 HttpSession의 이름을 mySession 이나 뭐 기타 등등으로 바꾸면 됩니다.

 

★ 혹은, HttpSession을 쓰지 않고, JSP 내장 객체의 session을 사용하면 되겠는데, 이 때 jsp 내장객체의 session객체는 isNew()라는 메소드를 제공함으로써 세션의 존재 유무 혹은 세션이 현재 페이지에서 만들어졌는지 등을 알 수 있습니다.

 

 HttpSessionBindingListener 

jsp, 자바 빈( scope=application ) 하는 경우입니다.

굳이 db에 테이블을 생성하지 않아도 접속자 수를 실시간으로 구하실 수 있으실 겁니다.

 

 

=== SessionChecker.java  ===============================

import java.io.*;
import java.util.*;
import javax.servlet.http.*;

public class SessionChecker{
    public void setSession(HttpSession session){
 // 리스너 객체를 생성해서 이것도 세션에 같이 담는다.  리스너 라는 이름으로...
        session.setAttribute("listener", new CustomBindingListener());
    }
}

 

//          여기서    구현했습니다..

class CustomBindingListener implements HttpSessionBindingListener {
    public void valueBound(HttpSessionBindingEvent event) {
 // 세션이 생겼을 할 내용
        System.out.println("BOUND as " + event.getName() + " to " + event.getSession().getId());
    }

    public void valueUnbound(HttpSessionBindingEvent event) {
 // 세션이 종료되었을때
        System.out.println("UNBOUND as " + event.getName() + " to " + event.getSession().getId());
    }
}

 


==== test.jsp =========================================


<%@ page contentType="text/html;charset=KS_C_5601-1987" %>
<jsp:useBean id="sc" class="SessionChecker" scope="application" />
<%
    session.setMaxInactiveInterval(60); // 걍 결과가 빨리 보고싶어서여.. 60초
    sc.setSession(session);
    out.println("세션 등록");
%>

 

test 페이지를 연 후 60초 뒤에 unbound 어쩌구의 메세지를 보실 수 있을 겁니다.

 

[출처 : OKJSP > http://www.okjsp.pe.kr/seq/20524]

 

 

 

출처 : http://blog.naver.com/korekiss/20038228350

 

HttpSessionBindingListener 는 웹에서 동시 사용자의 수 또는 하나의 아이디로 동시접속을 제한 할때 유용한 인터페이스 이다.  HttpSessionBindingListener 는 두개의 메소드를 지니는데 valueBound() 와 valueUnbound() 메소드 이다.

 
valueBound() 는 HttpSessionBindingListener 클래스의 인스턴스가 세션에 attribute로
등록될떄 호출된다  session.setAttribute(플래그, 값)
valueUnbound()는 session.removeAttribute(플래그); 사용시
또는 세션종료시  session.invalidate()호출된다.
 
다음은 이를 이용한 동시 사용자및 중복 로그인 방지 프로그램이다.
 
 
LoginManager.java
 

package itexpert.chap05;

 

import javax.servlet.http.HttpSession;

import javax.servlet.http.HttpSessionBindingListener;

import javax.servlet.http.HttpSessionBindingEvent;

import java.util.Hashtable;

import java.util.Enumeration;

 

       public class LoginManager implements HttpSessionBindingListener

       {

              private static LoginManager loginManager = null

              private static Hashtable loginUsers = new Hashtable();

              private LoginManager(){

              super();

       }

 

       public static synchronized LoginManager getInstance(){

              if(loginManager == null){

                     loginManager = new LoginManager();

              }

       return loginManager

       }

 

       //아이디가 맞는지 체크

       public boolean isValid(String userID, String userPW){

              return true //자세한 로직은 미구현

       }

 

       //해당 세션에 이미 로그인 되있는지 체크

       public boolean isLogin(String sessionID){ //세션 ID를 받습니다.

              boolean isLogin = false

              Enumeration e = loginUsers.keys();

              String key = ""

              while(e.hasMoreElements()){ //다음 데이터의 유무를 판단합니다.

                     key = (String)e.nextElement();//key변수에 대입.

                     if(sessionID.equals(key)){ //현재 세션 ID값과 로그인한 값을 비교합니다.

                            isLogin = true //해당되는 세션 아이디가 이미 등록되어 있다는 것을 의미.

                     }

              }

              return isLogin; //초기는 FALSE 이지요. WHILE문에서 걸러진다면 위에서 알아서 처리되겠습니다.

       }

 

       //중복 로그인 막기 위해 아이디 사용중인지 체크

       public boolean isUsing(String userID){

              boolean isUsing = false

              Enumeration e = loginUsers.keys();

              String key = ""

              while(e.hasMoreElements()){

                     key = (String)e.nextElement();

                     if(userID.equals(loginUsers.get(key))){//hashTable에 저장된 키값을 받는다는 것은

                            //키 값에 포함된 Value 값을 받는 것(즉, 사용자ID)

                            isUsing = true

                     }

              }

              return isUsing;

       }

 

       //세션 생성

       public void setSession(HttpSession session, String userID){

              loginUsers.put(session.getId(), userID) //[1] 세션 ID를 가져오고, [2] 사용자 ID를 가져와서 대입.

              session.setAttribute("login"this.getInstance()); //세션의 속성에 로그인매니져의 정보를 설정합니다.

       }

 

       //세션 성립될 때

       public void valueBound(HttpSessionBindingEvent event){ // 로그인 할때

       }

       //세션 끊길때

       public void valueUnbound(HttpSessionBindingEvent event){ //로그아웃 할때

              loginUsers.remove(event.getSession().getId());//이벤트가 발생을 시킨 세션의 ID 값을 가져와서 HashTable

                                                                          //데이터를 삭제합니다.

       }

 

       //세션 ID로 로긴된 ID 구분

       public String getUserID(String sessionID){

              return (String)loginUsers.get(sessionID);

       }

       //현재 접속자수

       public int getUserCount(){ //몇 명의 사용자가 로그인 하고 있는지 계산합니다.

              return loginUsers.size();

              }

}

Bind_login.jsp
 

<%@ page contentType="text/html;charset=euc-kr" import="itexpert.chap05.LoginManager"%>

<% LoginManager loginManager = LoginManager.getInstance(); %>

<html>

<body>

<center>

       현재 접속자수 : <%= loginManager.getUserCount() %><p>

<hr>

<%

       if(loginManager.isLogin(session.getId())){ //세션 아이디가 로그인 중이면

              out.println(loginManager.getUserID(session.getId())+"님 안녕하세요<br>"

              +"<a href=Bind_logout.jsp>로그아웃</a>");

       }

       else//그렇지 않으면 로그인 할 수 있도록

%>

<form name="login" action="Bind_login_ok.jsp">

아이디: <input type="text" name="userID"><br>

비밀번회: <input type="text" name="userPW"><br>

<input type="submit" value="로그인">

</form>

<% }%>

</center>

</body>

</html>

 

Bind_login_ok.jsp
 

<%@ page contentType="text/html;charset=euc-kr" import="itexpert.chap05.LoginManager"%>

 

<% LoginManager loginManager = LoginManager.getInstance(); %>

<%

       request.setCharacterEncoding("euc-kr");

       String userID = request.getParameter("userID");

       String userPW = request.getParameter("userPW");

       if(loginManager.isValid(userID, userPW)){ //ID와 비밀번호가 맞는지 확인합니다.

       if(!loginManager.isUsing(userID)){//ID가 사용중인지 아닌지 판단합니다.

       loginManager.setSession(session, userID); // 사용하지 않는다면, 현재 세션과 ID 저장

       response.sendRedirect("Bind_login.jsp"); //다시 로그인 화면으로 이동합니다.

       }

       else{

       //throw new Exception("이미 로그인중입니다.");

       response.sendRedirect(request.getContextPath()+"/index.jsp");

       }

}

else{

       //throw new Exception("ID/PW 이상");

       response.sendRedirect(request.getContextPath()+"/index.jsp");

}

%>

 

 
Bind_logout.jsp
 

<%@ page contentType="text/html;charset=euc-kr"%>

<%

       session.invalidate();

       response.sendRedirect("Bind_login.jsp");

%>

 
index.html

<%@ page language="java" contentType="text/html; charset=EUC-KR" import="itexpert.chap05.LoginManager"

pageEncoding="EUC-KR"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

 

<%

       LoginManager loginManager = LoginManager.getInstance();

%>

 

       <!-- Sessin ID : <%= session.getId() %> -->

 

<script>

function isLoginUserForm() {

       if(login.userID.value == "") {

              alert ("ID를 입력하지 않았습니다.");

              login.userID.focus();

              return false

       }

 

       if(login.userPW.value == "") {

       alert ("패스워드를 입력하지 않았습니다.");

       login.userPW.focus();

       return false

       }

       return true

}

 

</script>

 

 

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">

<title>방명록 작성 실습 예제 v0.01</title>

</head>

<body>

<center>

<h1> 방명록 작성 실습 예제 v0.03(JSP+JDBC + Login) </h1>

</center>

<hr/>

본 실습을 문서를 참고하지 않고 다시 작성할 수 있을때까지 반복하여 수행한다.

<br>

 

<hr/>

<href="guestController.do?action_code=insert&mode=1"> 방명록작성(JSP를 이용하여 오라클 DB에 자료를 입력하는 실습)</a>

<hr/>

<href="guestController.do?action_code=select"> 방명록보기(JSP를 이용하여 오라클 DB를 조회하는 실습)</a>

<hr/>

 

<h3>현재 접속자 수 : <%= loginManager.getUserCount() %> </h3>

<hr>

<%

if( loginManager.isLogin(session.getId())) {

%>

 

<%= loginManager.getUserID(session.getId()) %> 님 안녕하세요

<br>

<href="Bind_logout.jsp"> 로그아웃 </a>

 

<%

else {

%>

<form name="login" action="Bind_login_ok.jsp" onsubmit="return isLoginUserForm();" >

아이디 : <input type="text" name="userID"> 비밀번호 : <input type="password" name="userPW">

<input type="submit" value="로그인" >

</form>

 

현재 버전에서의 아이디와 패스워드는 데이터베이스를 점검하지 않고 있음. <p>

그러나 동일한 이름의 아이디를 이용한 중복 로그인은 허용하지 않음.

 

       <%

       }

       %>

 

</body>

</html>


출처 - http://kimjongyeol.tistory.com/94





Posted by linuxism
,