blob: 48feadc189e715957b314e5148bbbfc9edd2bc22 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bufio"
"crypto/hmac"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"golang.org/x/build"
"golang.org/x/build/revdial"
)
func dialCoordinator() error {
devMode := !strings.HasPrefix(*coordinator, "farmer.golang.org")
if *hostname == "" {
*hostname, _ = os.Hostname()
}
modes := strings.Split(*reverse, ",")
var keys []string
for _, m := range modes {
if devMode {
keys = append(keys, string(devBuilderKey(m)))
continue
}
keyPath := filepath.Join(homedir(), ".gobuildkey-"+m)
key, err := ioutil.ReadFile(keyPath)
if os.IsNotExist(err) && len(modes) == 1 {
globalKeyPath := filepath.Join(homedir(), ".gobuildkey")
key, err = ioutil.ReadFile(globalKeyPath)
if err != nil {
log.Fatalf("cannot read either key file %q or %q: %v", keyPath, globalKeyPath, err)
}
} else if err != nil {
log.Fatalf("cannot read key file %q: %v", keyPath, err)
}
keys = append(keys, string(key))
}
caCert := build.ProdCoordinatorCA
addr := *coordinator
if addr == "farmer.golang.org" {
addr = "farmer.golang.org:443"
}
if devMode {
caCert = build.DevCoordinatorCA
}
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM([]byte(caCert)) {
log.Fatal("failed to append coordinator CA certificate")
}
log.Printf("Dialing coordinator %s...", addr)
dialer := net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 15 * time.Second,
}
tcpConn, err := dialer.Dial("tcp", addr)
if err != nil {
return err
}
config := &tls.Config{
ServerName: "go",
RootCAs: caPool,
InsecureSkipVerify: devMode,
}
conn := tls.Client(tcpConn, config)
if err := conn.Handshake(); err != nil {
return fmt.Errorf("failed to handshake with coordinator: %v", err)
}
bufr := bufio.NewReader(conn)
req, err := http.NewRequest("GET", "/reverse", nil)
if err != nil {
log.Fatal(err)
}
req.Header["X-Go-Builder-Type"] = modes
req.Header["X-Go-Builder-Key"] = keys
req.Header.Set("X-Go-Builder-Hostname", *hostname)
req.Header.Set("X-Go-Builder-Version", strconv.Itoa(buildletVersion))
if err := req.Write(conn); err != nil {
return fmt.Errorf("coordinator /reverse request failed: %v", err)
}
resp, err := http.ReadResponse(bufr, req)
if err != nil {
return fmt.Errorf("coordinator /reverse response failed: %v", err)
}
if resp.StatusCode != 101 {
msg, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("coordinator registration failed; want HTTP status 101; got %v:\n\t%s", resp.Status, msg)
}
log.Printf("Connected to coordinator; reverse dialing active")
srv := &http.Server{}
err = srv.Serve(revdial.NewListener(bufio.NewReadWriter(
bufio.NewReader(conn),
bufio.NewWriter(conn),
)))
return fmt.Errorf("http.Serve on reverse connection complete: %v", err)
}
func devBuilderKey(builder string) string {
h := hmac.New(md5.New, []byte("gophers rule"))
io.WriteString(h, builder)
return fmt.Sprintf("%x", h.Sum(nil))
}
func homedir() string {
if runtime.GOOS == "windows" {
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
}
home := os.Getenv("HOME")
if home != "" {
return home
}
if os.Getuid() == 0 {
return "/root"
}
return "/"
}
// TestDialCoordinator dials the coordinator. Exported for testing.
func TestDialCoordinator() {
// TODO(crawshaw): move some of this logic out of main to simplify testing hook.
http.Handle("/status", http.HandlerFunc(handleStatus))
dialCoordinator()
}