Go로 TCP 프록시 만들기

10747 단어 5프록시
Go 공부로 TCP 프록시를 만들었습니다.
모든 TCP 연결 중에는 통신 로그를 캡처할 수 있습니다.

이런 이미지입니다.





구성요소


  • TCP 연결 수락
  • 수락 후 실제 서버에 연결
  • 그 후는 받아들인 Socket과 진짜 서버를 양방향으로 릴레이 한다.

    TCP 연결 수락



    우선은 외부로부터의 TCP 접속을 받아들이는 부분을 만듭니다.
    보통 TCP 서버를 만들 때와 같습니다.

    새로운 접속이 오면 handleConn 함수를 goroutine 로 실행하는 것으로 복수의 접속을 받아들여 가능하게 하고 있습니다.

    handleConn 의 내용은 이 뒤에 기술해 갑니다.
    type Server struct {}
    
    func (s *Server) Start(listenAddr *net.TCPAddr) error {
        lt, err := net.ListenTCP("tcp", listenAddr)
        if err != nil {
            return err
        }
        defer lt.Close()
    
        for {
            conn, err := lt.AcceptTCP()
            if err != nil {
                return err
            }
            go s.handleConn(conn)
        }
    }
    
    func (s *Server) handleConn(c *net.TCPConn) {
        ...
    }
    

    수락 후 양방향 릴레이



    클라이언트로부터 접속한 뒤는, 진짜의 서버에 접속해,
    클라이언트와 서버의 통신 내용을 릴레이(양방향으로 연결)합니다.

    양방향이라는 것은
  • 클라이언트 → 서버
  • 서버 → 클라이언트

  • 의 2방향이므로, 2개의 goroutine 를 병렬시켜 실시합니다.


    func (s *Server) handleConn(c *net.TCPConn) {
        defer c.Close()
    
        // 何らかの方法で本物のサーバーのアドレスを取得しておく
        serverAddr := ....
    
        // 本物のサーバーへ接続
        serverConn, err := net.DialTCP("tcp", nil, serverAddr)
        if err != nil {
            return err
        }
        defer serverConn.Close()
    
        // 双方向のリレーを作る
        p := &Proxy{}
        p.Start(c, serverConn)
    }
    
    // プロキシ本体
    type Proxy struct{}
    
    // プロキシの開始(双方向リレー開始)
    func (p *Proxy) Start(clientConn, serverConn) error {
        defer clientConn.Close()
        defer serverConn.Close()
    
        var eg errGroup.Group
    
        eg.Go(func() error { return p.relay(&eg, clientConn, serverConn) })
        eg.Go(func() error { return p.relay(&eg, serverConn, clientConn) })
    
        // wait for stop
        return eg.Wait()
    }
    
    const (
        BUFFER_SIZE = 0xFFFF
    )
    
    // fromConn -> toConn へ通信内容をリレーする
    func (p *Proxy) relay(eg *errGroup.Group, fromConn, toConn *net.TCPConn) error {
        buff := make([]byte, BUFFER_SIZE)
        for {
            n, err := fromConn.Read(buff)
            if err != nil {
                return err
            }
            b := buff[:n]
    
            // ここでログなどを取ることができる
    
            n, err = toConn.Write(b)
            if err != nil {
                return err
            }
        }
    }
    

    요약


  • 서버 부분과 릴레이 부분으로 나누어 생각하면 알기 쉽다
  • goroutine을 통한 통지는 channel을 잡는다
  • 여러 goroutine을 기다리고 싶다면 sync.WaitGroup

    참고로 한 페이지


  • 엣 rG루 p. G루p
  • 좋은 웹페이지 즐겨찾기