// Package haproxy implements the handshake for the HAProxy client protocol // version 1, as described in // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt. package haproxy import ( "bufio" "errors" "net" "strconv" "strings" ) var ( errInvalidProtoID = errors.New("invalid protocol identifier") errUnkProtocol = errors.New("unknown protocol") errInvalidFields = errors.New("invalid number of fields") errInvalidSrcIP = errors.New("invalid src ip") errInvalidDstIP = errors.New("invalid dst ip") errInvalidSrcPort = errors.New("invalid src port") errInvalidDstPort = errors.New("invalid dst port") ) // Handshake performs the HAProxy protocol v1 handshake on the given reader, // which is expected to be backed by a network connection. // It returns the source and destination addresses, or an error if the // handshake could not complete. // Note that any timeouts or limits must be set by the caller on the // underlying connection, this is helper only to perform the handshake. func Handshake(r *bufio.Reader) (src, dst net.Addr, err error) { line, err := r.ReadString('\n') if err != nil { return nil, nil, err } fields := strings.Fields(line) if len(fields) < 2 || fields[0] != "PROXY" { return nil, nil, errInvalidProtoID } switch fields[1] { case "TCP4", "TCP6": // Allowed to continue, nothing to do. default: return nil, nil, errUnkProtocol } if len(fields) != 6 { return nil, nil, errInvalidFields } srcIP := net.ParseIP(fields[2]) if srcIP == nil { return nil, nil, errInvalidSrcIP } dstIP := net.ParseIP(fields[3]) if dstIP == nil { return nil, nil, errInvalidDstIP } srcPort, err := strconv.ParseUint(fields[4], 10, 16) if err != nil { return nil, nil, errInvalidSrcPort } dstPort, err := strconv.ParseUint(fields[5], 10, 16) if err != nil { return nil, nil, errInvalidDstPort } src = &net.TCPAddr{IP: srcIP, Port: int(srcPort)} dst = &net.TCPAddr{IP: dstIP, Port: int(dstPort)} return src, dst, nil }