Procházet zdrojové kódy

Merge pull request #254 from rod-hynes/master

Add tunneled NTP request to UDP test cases
Rod Hynes před 9 roky
rodič
revize
d7c145e0a0
1 změnil soubory, kde provedl 111 přidání a 27 odebrání
  1. 111 27
      psiphon/server/server_test.go

+ 111 - 27
psiphon/server/server_test.go

@@ -21,6 +21,7 @@ package server
 
 import (
 	"encoding/json"
+	"errors"
 	"flag"
 	"fmt"
 	"io/ioutil"
@@ -28,6 +29,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"strconv"
 	"sync"
 	"syscall"
 	"testing"
@@ -381,19 +383,19 @@ func runServer(t *testing.T, runConfig *runServerConfig) {
 		}
 	}
 
-	// Test: tunneled UDP packet
+	// Test: tunneled UDP packets
 
 	udpgwServerAddress := serverConfig.(map[string]interface{})["UDPInterceptUdpgwServerAddress"].(string)
 
-	err = makeTunneledDNSRequest(t, localSOCKSProxyPort, udpgwServerAddress)
+	err = makeTunneledNTPRequest(t, localSOCKSProxyPort, udpgwServerAddress)
 
 	if err == nil {
 		if runConfig.denyTrafficRules {
-			t.Fatalf("unexpected tunneled DNS request success")
+			t.Fatalf("unexpected tunneled NTP request success")
 		}
 	} else {
 		if !runConfig.denyTrafficRules {
-			t.Fatalf("tunneled DNS request failed: %s", err)
+			t.Fatalf("tunneled NTP request failed: %s", err)
 		}
 	}
 }
@@ -429,9 +431,9 @@ func makeTunneledWebRequest(t *testing.T, localHTTPProxyPort int) error {
 	return nil
 }
 
-func makeTunneledDNSRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAddress string) error {
+func makeTunneledNTPRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAddress string) error {
 
-	testHostname := "psiphon.ca"
+	testHostname := "pool.ntp.org"
 	timeout := 5 * time.Second
 
 	localUDPProxyAddress, err := net.ResolveUDPAddr("udp", "127.0.0.1:7301")
@@ -439,11 +441,20 @@ func makeTunneledDNSRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAd
 		t.Fatalf("ResolveUDPAddr failed: %s", err)
 	}
 
-	go func() {
+	// Note: this proxy is intended for this test only -- it only accepts a single connection,
+	// handles it, and then terminates.
+
+	localUDPProxy := func(destinationIP net.IP, destinationPort uint16, waitGroup *sync.WaitGroup) {
+
+		if waitGroup != nil {
+			defer waitGroup.Done()
+		}
+
+		destination := net.JoinHostPort(destinationIP.String(), strconv.Itoa(int(destinationPort)))
 
 		serverUDPConn, err := net.ListenUDP("udp", localUDPProxyAddress)
 		if err != nil {
-			t.Logf("ListenUDP failed: %s", err)
+			t.Logf("ListenUDP for %s failed: %s", destination, err)
 			return
 		}
 		defer serverUDPConn.Close()
@@ -453,7 +464,7 @@ func makeTunneledDNSRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAd
 		packetSize, clientAddr, err := serverUDPConn.ReadFromUDP(
 			buffer[udpgwPreambleSize:len(buffer)])
 		if err != nil {
-			t.Logf("serverUDPConn.Read failed: %s", err)
+			t.Logf("serverUDPConn.Read for %s failed: %s", destination, err)
 			return
 		}
 
@@ -461,66 +472,139 @@ func makeTunneledDNSRequest(t *testing.T, localSOCKSProxyPort int, udpgwServerAd
 
 		dialer, err := proxy.SOCKS5("tcp", socksProxyAddress, nil, proxy.Direct)
 		if err != nil {
-			t.Logf("proxy.SOCKS5 failed: %s", err)
+			t.Logf("proxy.SOCKS5 for %s failed: %s", destination, err)
 			return
 		}
 
 		socksTCPConn, err := dialer.Dial("tcp", udpgwServerAddress)
 		if err != nil {
-			t.Logf("dialer.Dial failed: %s", err)
+			t.Logf("dialer.Dial for %s failed: %s", destination, err)
 			return
 		}
 		defer socksTCPConn.Close()
 
+		flags := uint8(0)
+		if destinationPort == 53 {
+			flags = udpgwProtocolFlagDNS
+		}
+
 		err = writeUdpgwPreamble(
 			udpgwPreambleSize,
-			udpgwProtocolFlagDNS,
+			flags,
 			0,
-			make([]byte, 4), // ignored due to transparent DNS forwarding
-			53,
+			destinationIP,
+			destinationPort,
 			uint16(packetSize),
 			buffer)
 		if err != nil {
-			t.Logf("writeUdpgwPreamble failed: %s", err)
+			t.Logf("writeUdpgwPreamble for %s failed: %s", destination, err)
 			return
 		}
 
 		_, err = socksTCPConn.Write(buffer[0 : udpgwPreambleSize+packetSize])
 		if err != nil {
-			t.Logf("socksTCPConn.Write failed: %s", err)
+			t.Logf("socksTCPConn.Write for %s failed: %s", destination, err)
 			return
 		}
 
 		updgwProtocolMessage, err := readUdpgwMessage(socksTCPConn, buffer)
 		if err != nil {
-			t.Logf("readUdpgwMessage failed: %s", err)
+			t.Logf("readUdpgwMessage for %s failed: %s", destination, err)
 			return
 		}
 
 		_, err = serverUDPConn.WriteToUDP(updgwProtocolMessage.packet, clientAddr)
 		if err != nil {
-			t.Logf("serverUDPConn.Write failed: %s", err)
+			t.Logf("serverUDPConn.Write for %s failed: %s", destination, err)
 			return
 		}
-	}()
+	}
 
-	// TODO: properly synchronize with server startup
+	// Tunneled DNS request
+
+	waitGroup := new(sync.WaitGroup)
+	waitGroup.Add(1)
+	go localUDPProxy(
+		net.IP(make([]byte, 4)), // ignored due to transparent DNS forwarding
+		53,
+		waitGroup)
+	// TODO: properly synchronize with local UDP proxy startup
 	time.Sleep(1 * time.Second)
 
 	clientUDPConn, err := net.DialUDP("udp", nil, localUDPProxyAddress)
 	if err != nil {
 		return fmt.Errorf("DialUDP failed: %s", err)
 	}
-	defer clientUDPConn.Close()
 
 	clientUDPConn.SetReadDeadline(time.Now().Add(timeout))
 	clientUDPConn.SetWriteDeadline(time.Now().Add(timeout))
 
-	_, _, err = psiphon.ResolveIP(testHostname, clientUDPConn)
+	addrs, _, err := psiphon.ResolveIP(testHostname, clientUDPConn)
+
+	clientUDPConn.Close()
+
+	if err == nil && (len(addrs) == 0 || len(addrs[0]) < 4) {
+		err = errors.New("no address")
+	}
 	if err != nil {
 		return fmt.Errorf("ResolveIP failed: %s", err)
 	}
 
+	waitGroup.Wait()
+
+	// Tunneled NTP request
+
+	go localUDPProxy(addrs[0][len(addrs[0])-4:], 123, nil)
+	// TODO: properly synchronize with local UDP proxy startup
+	time.Sleep(1 * time.Second)
+
+	clientUDPConn, err = net.DialUDP("udp", nil, localUDPProxyAddress)
+	if err != nil {
+		return fmt.Errorf("DialUDP failed: %s", err)
+	}
+
+	clientUDPConn.SetReadDeadline(time.Now().Add(timeout))
+	clientUDPConn.SetWriteDeadline(time.Now().Add(timeout))
+
+	// NTP protocol code from: https://groups.google.com/d/msg/golang-nuts/FlcdMU5fkLQ/CAeoD9eqm-IJ
+
+	ntpData := make([]byte, 48)
+	ntpData[0] = 3<<3 | 3
+
+	_, err = clientUDPConn.Write(ntpData)
+	if err != nil {
+		clientUDPConn.Close()
+		return fmt.Errorf("NTP Write failed: %s", err)
+	}
+
+	_, err = clientUDPConn.Read(ntpData)
+	if err != nil {
+		clientUDPConn.Close()
+		return fmt.Errorf("NTP Read failed: %s", err)
+	}
+
+	clientUDPConn.Close()
+
+	var sec, frac uint64
+	sec = uint64(ntpData[43]) | uint64(ntpData[42])<<8 | uint64(ntpData[41])<<16 | uint64(ntpData[40])<<24
+	frac = uint64(ntpData[47]) | uint64(ntpData[46])<<8 | uint64(ntpData[45])<<16 | uint64(ntpData[44])<<24
+
+	nsec := sec * 1e9
+	nsec += (frac * 1e9) >> 32
+
+	ntpNow := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nsec)).Local()
+
+	now := time.Now()
+
+	diff := ntpNow.Sub(now)
+	if diff < 0 {
+		diff = -diff
+	}
+
+	if diff > 1*time.Minute {
+		return fmt.Errorf("Unexpected NTP time: %s; local time: %s", ntpNow, now)
+	}
+
 	return nil
 }
 
@@ -560,12 +644,12 @@ func pavePsinetDatabaseFile(t *testing.T, psinetFilename string) (string, string
 
 func paveTrafficRulesFile(t *testing.T, trafficRulesFilename, sponsorID string, deny bool) {
 
-	allowTCPPort := "443"
-	allowUDPPort := "53"
+	allowTCPPorts := "443"
+	allowUDPPorts := "53, 123"
 
 	if deny {
-		allowTCPPort = "0"
-		allowUDPPort = "0"
+		allowTCPPorts = "0"
+		allowUDPPorts = "0"
 	}
 
 	trafficRulesJSONFormat := `
@@ -601,7 +685,7 @@ func paveTrafficRulesFile(t *testing.T, trafficRulesFilename, sponsorID string,
     `
 
 	trafficRulesJSON := fmt.Sprintf(
-		trafficRulesJSONFormat, sponsorID, allowTCPPort, allowUDPPort)
+		trafficRulesJSONFormat, sponsorID, allowTCPPorts, allowUDPPorts)
 
 	err := ioutil.WriteFile(trafficRulesFilename, []byte(trafficRulesJSON), 0600)
 	if err != nil {