• 2008-03-21

    SVN MKACTIVITY 403 Forbidden

    회사에서 Collabnet Subversion 에 SSPI 를 연동해서 별 문제없이 사용하던 어느 날, 저장소에 Boost 1.34.1 트리를 넣고 난 다음부터 update/commit 이 무지막지하게 느려졌었다. 혹시나 신 버전에서는 해결된 문제가 아닐까 해서 Collabnet 을 업그레이드했으나, 역시나 파일 개수가 많아서 생긴 문제였고, 날 잡아서 사용하지 않는 서드파티 라이브러리들을 모조리 삭제하고 난 후에야 그럭저럭 제 속도를 얻을 수 있었다.

    그러나, 이게 끝이 아니었으니,  갑자기 미국에 출장간 일부 개발자들이 update는 되는데 commit이 안된다는 보고를 해왔다. 아파치 로그를 살펴보니

    Access denied: '' MKACTIVITY :

    라는 메시지가 나왔다. 그렇다고 모두 안되면 다시 롤백이라도 하겠는데, 특정 사용자만 안되었고, 새로 만든 아이디를 만들어서 권한을 부여해도 안되고, 그런데 또 관리자 계정을 사용하면 되는 기묘한 현상이었다.

    3-4 시간 정도 구글링해본 결과 도무지 해답을 찾을 수가 없어서, 포기하고 Collabnet Enterprise Subversion 을 구매하려다가 놀랄만한 가격 때문에 좌절해있던 찰라, 문제를 보고한 개발자가 한방에 구글에서 해답을 찾아내었다.  정답은 subversion 서버의 버전이 올라가면서 레파지토리 이름의 대소문자를 타이트하게 구분하게 되면서 생긴 문제였다. 결국 relocating만 하면 해결되는 것이었다는 슬픈 전설이 전해진다.

    이 이야기의 교훈은, 구글 검색어를 어떻게 조합하냐에 따라 O(N^2)으로 해결할 문제도 O(1)로 해결할  수 있다..일까?

  • 2008-03-16

    boost::asio udp relay server

    조만간 회사에서 쓸 일이 있을 것 같아서 만들어본 udp relay 서버. 하는 일이라고는 패킷을 잠시 저장해뒀다가 원래 주소로 쏘는 일인데, ASIO를 쓰면 얼마나 코드가 간단해질까 해서 만들어봤다. 이 코드는 서버에만 쓰고, 클라이언트에 붙일 넘은 IOCP까지는 필요없고 그냥 Win32 + winsock2 + 폴링 쓰레드로 만들어볼까 싶다.

    
    class udp_relay_server
    {
        struct relay_policy
        {
            udp::endpoint from_, to_;
            DWORD delay_;
        };
    
        struct relay_message
        {
            boost::shared_ptr<std::string> message_;
            udp::endpoint destination_;
            DWORD expire_clock_;
        };
    
        enum
        {
            MIN_SLEEP_DELAY = 10
        };
    
    public:
    
        udp_relay_server(boost::asio::io_service& io_service, short port)
            : socket_(io_service, udp::endpoint(udp::v4(), port))
            , timer_(io_service)
        {
            do_receive();
            do_timer();
        }
    
        udp_relay_server(boost::asio::io_service& io_service, const udp::endpoint & endpoint_ )
            : socket_(io_service, endpoint_)
            , timer_(io_service)
        {
            do_receive();
            do_timer();
        }
    
        void add_policy( const udp::endpoint & from_, const udp::endpoint & to_, DWORD delay = 0 )
        {
            relay_policy p;
            p.from_ = from_;
            p.to_ = to_;
            p.delay_ = delay;
            policies.push_back(p);
        }
    
        void add_policy( string from_addr, short from_port, string to_addr, short to_port, DWORD delay = 0 )
        {
            udp::endpoint from_( boost::asio::ip::address::from_string(from_addr), from_port );
            udp::endpoint to_( boost::asio::ip::address::from_string(to_addr), to_port );
            add_policy( from_, to_, delay );
        }
    
        void clear_policy() { policies.clear(); }
    
    private:
    
        void do_timer()
        {
            timer_.expires_from_now(boost::posix_time::milliseconds(MIN_SLEEP_DELAY));
            timer_.async_wait(boost::bind(&udp_relay_server::handle_timer,this,boost::asio::placeholders::error));
        }
    
        void handle_timer( const boost::system::error_code & ec )
        {
            if ( !queue_.empty() )
            {
                DWORD cur_clock = GetTickCount();
                std::list< relay_message >::iterator itr = queue_.begin();
                while ( itr != queue_.end() )
                {
                    relay_message & m = *itr;
                    if ( cur_clock > m.expire_clock_ )
                    {
                        socket_.async_send_to(boost::asio::buffer(*m.message_), m.destination_,
                            boost::bind(&udp_relay_server::handle_send, this, m.message_, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) );
                        itr = queue_.erase(itr++);
                    }
                    else
                    {
                        itr++;
                    }
                }
            }
            do_timer();
        }
    
        void do_receive()
        {
            socket_.async_receive_from(
                boost::asio::buffer(recv_buffer_), remote_endpoint_,
                boost::bind(&udp_relay_server::handle_receive, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
        }
    
        void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred)
        {
            if (!error)
            {
                bool bRelayed = false;
                for(size_t i = 0 ; i < policies.size() ; i ++ )
                {
                    relay_policy & p = policies[i];
                    if ( p.from_ == remote_endpoint_ )
                    {
                        udp::endpoint relay_to(p.to_);
                        boost::shared_ptr<std::string> message(new string(recv_buffer_.data(),bytes_transferred));
                        if ( p.delay_ > 0 )
                        {
                            relay_message m;
                            m.message_ = message;
                            m.destination_ = relay_to;
                            m.expire_clock_ = GetTickCount() + p.delay_;
                            queue_.push_back( m );
                        }
                        else
                        {
                            do_send( message, relay_to );
                        }
                        bRelayed = true;
                        break;
                    }
                }
                do_receive();
            }
        }
    
        void do_send( boost::shared_ptr<std::string> message, const udp::endpoint & destination )
        {
            socket_.async_send_to(boost::asio::buffer(*message), destination,
                boost::bind(&udp_relay_server::handle_send, this,
                    message, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred) );
        }
    
        void handle_send(boost::shared_ptr<std::string> message, const boost::system::error_code& error, std::size_t bytes_transferred )
        {
        }
    
        udp::socket socket_;
        udp::endpoint remote_endpoint_;
        boost::array<char, 128> recv_buffer_;
        vector<relay_policy> policies;
        boost::asio::deadline_timer timer_;
        std::list< relay_message > queue_;
    };
    
  • 2008-03-01

    제가 참가했던 프로젝트가 CNN을 탔습니다

    http://nckstudio.com/wp-content/uploads/2008/02/ripken.jpg

    제가 몸담고 있는 회사에서 서비스중인 게임CNN 방송에 등장했습니다. 유투브에도 해당 방송 분량이 따로 올라왔네요. 언뜻 보기엔 좀 오래된 게임처럼 보이지만, 정말로 오래된 게임이라서 그렇습니다 :(

    저도 저 게임의 서버와 P2P, DB쪽 유지 보수 작업을 잠시 맡았다가, 지금은 저 게임의 후속작을 개발중입니다. 혹시나 MLB 2k7 이나 MLB 07 같은 리얼 온라인 야구 게임에 관심있는 개발자분들이 계시다면 연락 바랍드립니다. 참고로 미국에서는 불세출의 야구 영웅이라고 칭송받지만, 우리 회사에서는 그냥 횽아라고 불리는 칼 립켄 주니어는 저희 회사의 주주이기도 합니다.

    see also:

  • 2008-02-22

    WOW Warden as Spyware

    Exploiting Online Games: Cheating Massively Distributed Systems

    Exploiting Online Games에 와우의 보안 프로그램인 Warden이 어떻게 동작하는지에 대한 간략한 설명이 나와 있다.

    • 서버로부터 실시간으로 전송받는 듯하다. (스크립트일지도?)
    • 15초에 한번씩 사용자의 컴퓨터를 검색한다.
    • 와우 클라이언트의 주소 공간에 로딩된 모든 DLL 의 정보를 읽어들인다. (ToolHelp API)
    • 모든 윈도우 타이틀의 텍스트를 GetWindowTextA 로 읽어들인 다음, 해싱을 거쳐서 미리 등록된 금지 어플리케이션 목록과 비교한다.
    • 모든 프로세스의 코드 영역(0x004* 이나 0x0041*)의 10-20바이트를 읽어서, 역시 해싱을 거쳐 미리 등록된 금지 어플리케이션 목록과 비교한다.

    이 책의 저자는, 윈도우 타이틀에 SSN이나 카드 번호, URL, 이메일 등의 개인 정보가 블리자드로 빠져나갈 수 있고, 또한 내가 실행하고 있는 프로세스의 기밀 정보가 유출될 수 있기에, Warden이 안티해킹툴이라기 보다는 스파이웨어라고 주장하고 있다.

    한 명의 게이머의 입장에서는 저자의 주장에 수긍이 가지만, 이런 저런 해킹에 시달리는 게임 개발자의 관점에서 보면 이런 통계 기반의 해킹 체크도 한번 도입해보면 재미있지 않을까 하는 호기심도 솟아오른다. 우헷.

  • 2008-02-22

    boost::asio documentation

    doxygen 환경 파일직접 컴파일한 CHM 파일을 구글 코드에 올려뒀으니 관심있는 분들의 많은 애용 바랍니다.

    단, chm 파일이 80메가가 넘으므로, 환경 파일을 받고 OUTPUT_DIRECTORY라든지 STRIP_FROM_PATH 등을 적당히 고친 후 직접 컴파일하는게 나을 수도 있겠네요 :)

    제 홈페이지에서도 직접 받으실 수 있습니다.