Просмотр исходного кода

Add packet tunnel client-side transparent DNS rewriting

Allows VPN clients to specify an arbitrary DNS server IP instead of requiring
10.0.0.2, which can conflict with some LAN private address spaces.
Rod Hynes 3 лет назад
Родитель
Сommit
b12159a3cf

+ 0 - 12
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h

@@ -408,18 +408,6 @@ Returns the path where the rotated notices file will be created.
  */
  */
 - (long)getPacketTunnelMTU;
 - (long)getPacketTunnelMTU;
 
 
-/*!
- Only valid in whole device mode. Provides the DNS resolver IP address that is provided by the packet tunnel to the device.
-  @return  The IP address of the DNS resolver as a string.
- */
-- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv4Address;
-
-/*!
- Only valid in whole device mode. Provides the DNS resolver IP address that is provided by the packet tunnel to the device.
- @return  The IP address of the DNS resolver as a string.
- */
-- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv6Address;
-
 /*!
 /*!
  Provides the tunnel-core build info json as a string. See the tunnel-core build info code for details https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/common/buildinfo.go.
  Provides the tunnel-core build info json as a string. See the tunnel-core build info code for details https://github.com/Psiphon-Labs/psiphon-tunnel-core/blob/master/psiphon/common/buildinfo.go.
  @return  The build info json as a string.
  @return  The build info json as a string.

+ 0 - 10
MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m

@@ -457,16 +457,6 @@ typedef NS_ERROR_ENUM(PsiphonTunnelErrorDomain, PsiphonTunnelErrorCode) {
     return GoPsiGetPacketTunnelMTU();
     return GoPsiGetPacketTunnelMTU();
 }
 }
 
 
-// See comment in header.
-- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv4Address {
-    return GoPsiGetPacketTunnelDNSResolverIPv4Address();
-}
-
-// See comment in header.
-- (NSString * _Nonnull)getPacketTunnelDNSResolverIPv6Address {
-    return GoPsiGetPacketTunnelDNSResolverIPv6Address();
-}
-
 // See comment in header.
 // See comment in header.
 + (NSString * _Nonnull)getBuildInfo {
 + (NSString * _Nonnull)getBuildInfo {
     return GoPsiGetBuildInfo();
     return GoPsiGetBuildInfo();

+ 0 - 8
MobileLibrary/psi/psi.go

@@ -481,14 +481,6 @@ func GetPacketTunnelMTU() int {
 	return tun.DEFAULT_MTU
 	return tun.DEFAULT_MTU
 }
 }
 
 
-func GetPacketTunnelDNSResolverIPv4Address() string {
-	return tun.GetTransparentDNSResolverIPv4Address().String()
-}
-
-func GetPacketTunnelDNSResolverIPv6Address() string {
-	return tun.GetTransparentDNSResolverIPv6Address().String()
-}
-
 // WriteRuntimeProfiles writes Go runtime profile information to a set of
 // WriteRuntimeProfiles writes Go runtime profile information to a set of
 // files in the specified output directory. See common.WriteRuntimeProfiles
 // files in the specified output directory. See common.WriteRuntimeProfiles
 // for more details.
 // for more details.

+ 143 - 17
psiphon/common/tun/tun.go

@@ -87,7 +87,7 @@ the assigned IP address. The server also rewrites the destination address of
 certain DNS packets. The purpose of this is to allow clients to reconnect
 certain DNS packets. The purpose of this is to allow clients to reconnect
 to different servers without having to tear down or change their local
 to different servers without having to tear down or change their local
 network configuration. Clients may configure their local tun device with an
 network configuration. Clients may configure their local tun device with an
-arbitrary IP address and a static DNS resolver address.
+arbitrary IP address and an arbitrary DNS resolver address.
 
 
 The server uses the 24-bit 10.0.0.0/8 IPv4 private address space to maximize
 The server uses the 24-bit 10.0.0.0/8 IPv4 private address space to maximize
 the number of addresses available, due to Psiphon client churn and minimum
 the number of addresses available, due to Psiphon client churn and minimum
@@ -849,6 +849,7 @@ func (server *Server) runDeviceDownstream() {
 		if !processPacket(
 		if !processPacket(
 			session.metrics,
 			session.metrics,
 			session,
 			session,
+			nil,
 			packetDirectionServerDownstream,
 			packetDirectionServerDownstream,
 			readPacket) {
 			readPacket) {
 			// Packet is rejected and dropped. Reason will be counted in metrics.
 			// Packet is rejected and dropped. Reason will be counted in metrics.
@@ -892,7 +893,7 @@ func (server *Server) runClientUpstream(session *session) {
 
 
 		// processPacket transparently rewrites the source address to the
 		// processPacket transparently rewrites the source address to the
 		// session's assigned address and rewrites the destination of any
 		// session's assigned address and rewrites the destination of any
-		// DNS packets destined to the target DNS resolver.
+		// DNS packets destined to the transparent DNS resolver.
 		//
 		//
 		// The first time the source address is rewritten, the original
 		// The first time the source address is rewritten, the original
 		// value is recorded so inbound packets can have the reverse
 		// value is recorded so inbound packets can have the reverse
@@ -903,6 +904,7 @@ func (server *Server) runClientUpstream(session *session) {
 		if !processPacket(
 		if !processPacket(
 			session.metrics,
 			session.metrics,
 			session,
 			session,
+			nil,
 			packetDirectionServerUpstream,
 			packetDirectionServerUpstream,
 			readPacket) {
 			readPacket) {
 
 
@@ -1101,18 +1103,6 @@ func (server *Server) convertIndexToIPv6Address(index int32) net.IP {
 			index&0xFF))
 			index&0xFF))
 }
 }
 
 
-// GetTransparentDNSResolverIPv4Address returns the static IPv4 address
-// to use as a DNS resolver when transparent DNS rewriting is desired.
-func GetTransparentDNSResolverIPv4Address() net.IP {
-	return transparentDNSResolverIPv4Address
-}
-
-// GetTransparentDNSResolverIPv6Address returns the static IPv6 address
-// to use as a DNS resolver when transparent DNS rewriting is desired.
-func GetTransparentDNSResolverIPv6Address() net.IP {
-	return transparentDNSResolverIPv6Address
-}
-
 type session struct {
 type session struct {
 	// Note: 64-bit ints used with atomic operations are placed
 	// Note: 64-bit ints used with atomic operations are placed
 	// at the start of struct to ensure 64-bit alignment.
 	// at the start of struct to ensure 64-bit alignment.
@@ -1861,7 +1851,7 @@ type ClientConfig struct {
 	// transparent source IP address and DNS rewriting, the tun
 	// transparent source IP address and DNS rewriting, the tun
 	// device may have any assigned IP address, but should be
 	// device may have any assigned IP address, but should be
 	// configured with the given MTU; and DNS should be configured
 	// configured with the given MTU; and DNS should be configured
-	// to use the transparent DNS target resolver addresses.
+	// to use the specified transparent DNS resolver addresses.
 	// Set TunFileDescriptor to <= 0 to ignore this parameter
 	// Set TunFileDescriptor to <= 0 to ignore this parameter
 	// and create and configure a tun device.
 	// and create and configure a tun device.
 	TunFileDescriptor int
 	TunFileDescriptor int
@@ -1874,6 +1864,18 @@ type ClientConfig struct {
 	// assign to a newly created tun device.
 	// assign to a newly created tun device.
 	IPv6AddressCIDR string
 	IPv6AddressCIDR string
 
 
+	// TransparentDNSIPv4Address is the IPv4 address of the DNS server
+	// configured by a VPN using a packet tunnel. All DNS packets
+	// destined to this DNS server are transparently redirected to
+	// the Psiphon server DNS.
+	TransparentDNSIPv4Address string
+
+	// TransparentDNSIPv4Address is the IPv6 address of the DNS server
+	// configured by a VPN using a packet tunnel. All DNS packets
+	// destined to this DNS server are transparently redirected to
+	// the Psiphon server DNS.
+	TransparentDNSIPv6Address string
+
 	// RouteDestinations are hosts (IPs) or networks (CIDRs)
 	// RouteDestinations are hosts (IPs) or networks (CIDRs)
 	// to be configured to be routed through a newly
 	// to be configured to be routed through a newly
 	// created tun device.
 	// created tun device.
@@ -1885,6 +1887,7 @@ type ClientConfig struct {
 // tunnel server via a transport channel.
 // tunnel server via a transport channel.
 type Client struct {
 type Client struct {
 	config          *ClientConfig
 	config          *ClientConfig
+	transparentDNS  *clientTransparentDNS
 	device          *Device
 	device          *Device
 	channel         *Channel
 	channel         *Channel
 	upstreamPackets *PacketQueue
 	upstreamPackets *PacketQueue
@@ -1894,6 +1897,41 @@ type Client struct {
 	workers         *sync.WaitGroup
 	workers         *sync.WaitGroup
 }
 }
 
 
+// clientTransparentDNS caches the parsed representions of
+// TransparentDNSIPv4/6Address for fast packet processing and rewriting.
+type clientTransparentDNS struct {
+	IPv4Address net.IP
+	IPv6Address net.IP
+}
+
+func newClientTransparentDNS(
+	IPv4Address, IPv6Address string) (*clientTransparentDNS, error) {
+
+	var IPv4, IPv6 net.IP
+
+	if IPv4Address != "" {
+		IPv4 = net.ParseIP(IPv4Address)
+		if IPv4 != nil {
+			IPv4 = IPv4.To4()
+		}
+		if IPv4 == nil {
+			return nil, errors.TraceNew("invalid IPv4 address")
+		}
+	}
+
+	if IPv6Address != "" {
+		IPv6 = net.ParseIP(IPv6Address)
+		if IPv6 == nil || IPv6.To4() != nil {
+			return nil, errors.TraceNew("invalid IPv6 address")
+		}
+	}
+
+	return &clientTransparentDNS{
+		IPv4Address: IPv4,
+		IPv6Address: IPv6,
+	}, nil
+}
+
 // NewClient initializes a new Client. Unless using the
 // NewClient initializes a new Client. Unless using the
 // TunFileDescriptor configuration parameter, a new tun
 // TunFileDescriptor configuration parameter, a new tun
 // device is created for the client.
 // device is created for the client.
@@ -1917,10 +1955,18 @@ func NewClient(config *ClientConfig) (*Client, error) {
 		upstreamPacketQueueSize = config.UpstreamPacketQueueSize
 		upstreamPacketQueueSize = config.UpstreamPacketQueueSize
 	}
 	}
 
 
+	transparentDNS, err := newClientTransparentDNS(
+		config.TransparentDNSIPv4Address,
+		config.TransparentDNSIPv6Address)
+	if err != nil {
+		return nil, errors.Trace(err)
+	}
+
 	runContext, stopRunning := context.WithCancel(context.Background())
 	runContext, stopRunning := context.WithCancel(context.Background())
 
 
 	return &Client{
 	return &Client{
 		config:          config,
 		config:          config,
+		transparentDNS:  transparentDNS,
 		device:          device,
 		device:          device,
 		channel:         NewChannel(config.Transport, getMTU(config.MTU)),
 		channel:         NewChannel(config.Transport, getMTU(config.MTU)),
 		upstreamPackets: NewPacketQueue(upstreamPacketQueueSize),
 		upstreamPackets: NewPacketQueue(upstreamPacketQueueSize),
@@ -1965,6 +2011,7 @@ func (client *Client) Start() {
 			if !processPacket(
 			if !processPacket(
 				client.metrics,
 				client.metrics,
 				nil,
 				nil,
+				client.transparentDNS,
 				packetDirectionClientUpstream,
 				packetDirectionClientUpstream,
 				readPacket) {
 				readPacket) {
 				continue
 				continue
@@ -2029,6 +2076,7 @@ func (client *Client) Start() {
 			if !processPacket(
 			if !processPacket(
 				client.metrics,
 				client.metrics,
 				nil,
 				nil,
+				client.transparentDNS,
 				packetDirectionClientDownstream,
 				packetDirectionClientDownstream,
 				readPacket) {
 				readPacket) {
 				continue
 				continue
@@ -2280,6 +2328,7 @@ func getPacketDestinationIPAddress(
 func processPacket(
 func processPacket(
 	metrics *packetMetrics,
 	metrics *packetMetrics,
 	session *session,
 	session *session,
+	clientTransparentDNS *clientTransparentDNS,
 	direction packetDirection,
 	direction packetDirection,
 	packet []byte) bool {
 	packet []byte) bool {
 
 
@@ -2438,6 +2487,27 @@ func processPacket(
 	// - The traffic rules checks are bypassed, since transparent
 	// - The traffic rules checks are bypassed, since transparent
 	//   DNS is essential
 	//   DNS is essential
 
 
+	// Transparent DNS is a two-step translation. On the client, the VPN
+	// can be configured with any private address range, so as to not
+	// conflict with other local networks, such as WiFi. For example, the
+	// client may select from 192.168.0.0/16, when an existing interface
+	// uses a subnet in 10.0.0.0/8, and specify the VPN DNS server as 192.168.0.1.
+	//
+	// The first translation, on the client side, rewrites packets
+	// destined to 192.168.0.1:53, the DNS server, to the destination
+	// transparentDNSResolverIPv4Address:53. This packet is sent to the
+	// server.
+	//
+	// The second translation, on the server side, rewrites packets
+	// destined to transparentDNSResolverIPv4Address:53 to an actual DNS
+	// server destination.
+	//
+	// Then, reverse rewrites are applied to DNS response packets: the
+	// server rewrites the source address actual-DNS-server:53 to
+	// transparentDNSResolverIPv4Address:53, and then the client rewrites
+	// the source address transparentDNSResolverIPv4Address:53 to
+	// 192.168.0.1:53, and that packet is written to the tun device.
+
 	doTransparentDNS := false
 	doTransparentDNS := false
 
 
 	if isServer {
 	if isServer {
@@ -2520,6 +2590,34 @@ func processPacket(
 				}
 				}
 			}
 			}
 		}
 		}
+
+	} else { // isClient
+
+		if direction == packetDirectionClientUpstream {
+
+			// DNS packets destined to the configured VPN DNS servers,
+			// specified in clientTransparentDNS, are rewritten to go to
+			// transparentDNSResolverIPv4/6Address.
+
+			if destinationPort == portNumberDNS {
+				if (version == 4 && destinationIPAddress.Equal(clientTransparentDNS.IPv4Address)) ||
+					(version == 6 && destinationIPAddress.Equal(clientTransparentDNS.IPv6Address)) {
+					doTransparentDNS = true
+				}
+			}
+
+		} else { // packetDirectionClientDownstream
+
+			// DNS packets with a transparentDNSResolverIPv4/6Address source
+			// address are rewritten to come from the configured VPN DNS servers.
+
+			if sourcePort == portNumberDNS {
+				if (version == 4 && sourceIPAddress.Equal(transparentDNSResolverIPv4Address)) ||
+					(version == 6 && sourceIPAddress.Equal(transparentDNSResolverIPv6Address)) {
+					doTransparentDNS = true
+				}
+			}
+		}
 	}
 	}
 
 
 	// Apply rewrites before determining flow ID to ensure that corresponding up-
 	// Apply rewrites before determining flow ID to ensure that corresponding up-
@@ -2573,7 +2671,7 @@ func processPacket(
 
 
 		if version == 4 {
 		if version == 4 {
 			rewriteDestinationIPAddress = session.getOriginalIPv4Address()
 			rewriteDestinationIPAddress = session.getOriginalIPv4Address()
-		} else { // version == 6
+		} else if version == 6 {
 			rewriteDestinationIPAddress = session.getOriginalIPv6Address()
 			rewriteDestinationIPAddress = session.getOriginalIPv6Address()
 		}
 		}
 
 
@@ -2589,10 +2687,38 @@ func processPacket(
 
 
 			if version == 4 {
 			if version == 4 {
 				rewriteSourceIPAddress = transparentDNSResolverIPv4Address
 				rewriteSourceIPAddress = transparentDNSResolverIPv4Address
-			} else { // version == 6
+			} else if version == 6 {
 				rewriteSourceIPAddress = transparentDNSResolverIPv6Address
 				rewriteSourceIPAddress = transparentDNSResolverIPv6Address
 			}
 			}
 		}
 		}
+
+	} else if direction == packetDirectionClientUpstream {
+
+		// Rewrite the destination address to be
+		// transparentDNSResolverIPv4/6Address, which the server will
+		// subsequently send on to actual DNS servers.
+
+		if doTransparentDNS {
+
+			if version == 4 {
+				rewriteDestinationIPAddress = transparentDNSResolverIPv4Address
+			} else if version == 6 {
+				rewriteDestinationIPAddress = transparentDNSResolverIPv6Address
+			}
+		}
+	} else if direction == packetDirectionClientDownstream {
+
+		// Rewrite the source address so the DNS response appears to come from
+		// the configured VPN DNS server.
+
+		if doTransparentDNS {
+
+			if version == 4 {
+				rewriteSourceIPAddress = clientTransparentDNS.IPv4Address
+			} else if version == 6 {
+				rewriteSourceIPAddress = clientTransparentDNS.IPv6Address
+			}
+		}
 	}
 	}
 
 
 	// Check if flow is tracked before checking traffic permission
 	// Check if flow is tracked before checking traffic permission

+ 14 - 5
psiphon/common/tun/tun_test.go

@@ -110,7 +110,7 @@ func testTunneledTCP(t *testing.T, useIPv6 bool) {
 
 
 	var flowCounter bytesTransferredCounter
 	var flowCounter bytesTransferredCounter
 
 
-	flowActivityUpdaterMaker := func(_ string, IPAddress net.IP) []FlowActivityUpdater {
+	flowActivityUpdaterMaker := func(_ bool, _ string, IPAddress net.IP) []FlowActivityUpdater {
 
 
 		if IPAddress.String() != testTCPServer.getListenerIPAddress() {
 		if IPAddress.String() != testTCPServer.getListenerIPAddress() {
 			t.Fatalf("unexpected flow IP address")
 			t.Fatalf("unexpected flow IP address")
@@ -485,6 +485,13 @@ type testClient struct {
 	tunClient *Client
 	tunClient *Client
 }
 }
 
 
+const (
+	clientIPv4AddressCIDR           = "172.16.0.1/24"
+	clientIPv6AddressCIDR           = "fd26:b6a6:4454:310a:0000:0000:0000:0001/64"
+	clientTransparentDNSIPv4Address = "172.16.0.2"
+	clientTransparentDNSIPv6Address = "fd26:b6a6:4454:310a:0000:0000:0000:0002"
+)
+
 func startTestClient(
 func startTestClient(
 	useIPv6 bool,
 	useIPv6 bool,
 	MTU int,
 	MTU int,
@@ -505,8 +512,10 @@ func startTestClient(
 		Logger:                          logger,
 		Logger:                          logger,
 		SudoNetworkConfigCommands:       os.Getenv("TUN_TEST_SUDO") != "",
 		SudoNetworkConfigCommands:       os.Getenv("TUN_TEST_SUDO") != "",
 		AllowNoIPv6NetworkConfiguration: !useIPv6,
 		AllowNoIPv6NetworkConfiguration: !useIPv6,
-		IPv4AddressCIDR:                 "172.16.0.1/24",
-		IPv6AddressCIDR:                 "fd26:b6a6:4454:310a:0000:0000:0000:0001/64",
+		IPv4AddressCIDR:                 clientIPv4AddressCIDR,
+		IPv6AddressCIDR:                 clientIPv6AddressCIDR,
+		TransparentDNSIPv4Address:       clientTransparentDNSIPv4Address,
+		TransparentDNSIPv6Address:       clientTransparentDNSIPv6Address,
 		RouteDestinations:               routeDestinations,
 		RouteDestinations:               routeDestinations,
 		Transport:                       unixConn,
 		Transport:                       unixConn,
 		MTU:                             MTU,
 		MTU:                             MTU,
@@ -719,11 +728,11 @@ func testDNSClient(useIPv6 bool, tunDeviceName string) error {
 	var sockAddr syscall.Sockaddr
 	var sockAddr syscall.Sockaddr
 
 
 	if !useIPv6 {
 	if !useIPv6 {
-		copy(ipv4[:], transparentDNSResolverIPv4Address)
+		copy(ipv4[:], net.ParseIP(clientTransparentDNSIPv4Address).To4())
 		domain = syscall.AF_INET
 		domain = syscall.AF_INET
 		sockAddr = &syscall.SockaddrInet4{Addr: ipv4, Port: portNumberDNS}
 		sockAddr = &syscall.SockaddrInet4{Addr: ipv4, Port: portNumberDNS}
 	} else {
 	} else {
-		copy(ipv6[:], transparentDNSResolverIPv6Address)
+		copy(ipv6[:], net.ParseIP(clientTransparentDNSIPv6Address))
 		domain = syscall.AF_INET6
 		domain = syscall.AF_INET6
 		sockAddr = &syscall.SockaddrInet6{Addr: ipv6, Port: portNumberDNS}
 		sockAddr = &syscall.SockaddrInet6{Addr: ipv6, Port: portNumberDNS}
 	}
 	}

+ 12 - 0
psiphon/config.go

@@ -488,6 +488,18 @@ type Config struct {
 	// set, TunnelPoolSize must be 1.
 	// set, TunnelPoolSize must be 1.
 	PacketTunnelTunFileDescriptor int
 	PacketTunnelTunFileDescriptor int
 
 
+	// PacketTunnelTransparentDNSIPv4Address is the IPv4 address of the DNS
+	// server configured by a VPN using a packet tunnel. All DNS packets
+	// destined to this DNS server are transparently redirected to the
+	// Psiphon server DNS.
+	PacketTunnelTransparentDNSIPv4Address string
+
+	// PacketTunnelTransparentDNSIPv6Address is the IPv6 address of the DNS
+	// server configured by a VPN using a packet tunnel. All DNS packets
+	// destined to this DNS server are transparently redirected to the
+	// Psiphon server DNS.
+	PacketTunnelTransparentDNSIPv6Address string
+
 	// SessionID specifies a client session ID to use in the Psiphon API. The
 	// SessionID specifies a client session ID to use in the Psiphon API. The
 	// session ID should be a randomly generated value that is used only for a
 	// session ID should be a randomly generated value that is used only for a
 	// single session, which is defined as the period between a user starting
 	// single session, which is defined as the period between a user starting

+ 5 - 4
psiphon/controller.go

@@ -180,9 +180,11 @@ func NewController(config *Config) (controller *Controller, err error) {
 		packetTunnelTransport := NewPacketTunnelTransport()
 		packetTunnelTransport := NewPacketTunnelTransport()
 
 
 		packetTunnelClient, err := tun.NewClient(&tun.ClientConfig{
 		packetTunnelClient, err := tun.NewClient(&tun.ClientConfig{
-			Logger:            NoticeCommonLogger(),
-			TunFileDescriptor: config.PacketTunnelTunFileDescriptor,
-			Transport:         packetTunnelTransport,
+			Logger:                    NoticeCommonLogger(),
+			TunFileDescriptor:         config.PacketTunnelTunFileDescriptor,
+			TransparentDNSIPv4Address: config.PacketTunnelTransparentDNSIPv4Address,
+			TransparentDNSIPv6Address: config.PacketTunnelTransparentDNSIPv6Address,
+			Transport:                 packetTunnelTransport,
 		})
 		})
 		if err != nil {
 		if err != nil {
 			return nil, errors.Trace(err)
 			return nil, errors.Trace(err)
@@ -481,7 +483,6 @@ fetcherLoop:
 //
 //
 // TODO: refactor upgrade downloader and remote server list fetcher to use
 // TODO: refactor upgrade downloader and remote server list fetcher to use
 // common code (including the resumable download routines).
 // common code (including the resumable download routines).
-//
 func (controller *Controller) upgradeDownloader() {
 func (controller *Controller) upgradeDownloader() {
 	defer controller.runWaitGroup.Done()
 	defer controller.runWaitGroup.Done()