@ -670,22 +670,71 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Re
return filter . DropSilently
return filter . DropSilently
}
}
// If it's not traffic to the service IP (e.g. magicDNS or Taildrive) we don't
// Determine if we care about this local packet.
// care; resume processing.
dst := p . Dst . Addr ( )
if dst := p . Dst . Addr ( ) ; dst != serviceIP && dst != serviceIPv6 {
switch {
return filter . Accept
case dst == serviceIP || dst == serviceIPv6 :
}
// We want to intercept some traffic to the "service IP" (e.g.
// Of traffic to the service IP, we only care about UDP 53, and TCP
// 100.100.100.100 for IPv4). However, of traffic to the
// on port 53, 80, and 8080.
// service IP, we only care about UDP 53, and TCP on port 53,
switch p . IPProto {
// 80, and 8080.
case ipproto . TCP :
switch p . IPProto {
if port := p . Dst . Port ( ) ; port != 53 && port != 80 && port != 8080 {
case ipproto . TCP :
return filter . Accept
if port := p . Dst . Port ( ) ; port != 53 && port != 80 && port != 8080 {
return filter . Accept
}
case ipproto . UDP :
if port := p . Dst . Port ( ) ; port != 53 {
return filter . Accept
}
}
case viaRange . Contains ( dst ) :
// We need to handle 4via6 packets leaving the host if the via
// route is for this host; otherwise the packet will be dropped
// because nothing will translate it.
var shouldHandle bool
if p . IPVersion == 6 && ! ns . isLocalIP ( dst ) {
shouldHandle = ns . lb != nil && ns . lb . ShouldHandleViaIP ( dst )
}
}
case ipproto . UDP :
if ! shouldHandle {
if port := p . Dst . Port ( ) ; port != 53 {
// Unhandled means that we let the regular processing
// occur without doing anything ourselves.
return filter . Accept
return filter . Accept
}
}
if debugNetstack ( ) {
ns . logf ( "netstack: handling local 4via6 packet: version=%d proto=%v dst=%v src=%v" ,
p . IPVersion , p . IPProto , p . Dst , p . Src )
}
// If this is a ping message, handle it and don't pass to
// netstack.
pingIP , handlePing := ns . shouldHandlePing ( p )
if handlePing {
ns . logf ( "netstack: handling local 4via6 ping: dst=%v pingIP=%v" , dst , pingIP )
var pong [ ] byte // the reply to the ping, if our relayed ping works
if dst . Is4 ( ) {
h := p . ICMP4Header ( )
h . ToResponse ( )
pong = packet . Generate ( & h , p . Payload ( ) )
} else if dst . Is6 ( ) {
h := p . ICMP6Header ( )
h . ToResponse ( )
pong = packet . Generate ( & h , p . Payload ( ) )
}
go ns . userPing ( pingIP , pong , userPingDirectionInbound )
return filter . DropSilently
}
// Fall through to writing inbound so netstack handles the
// 4via6 via connection.
default :
// Not traffic to the service IP or a 4via6 IP, so we don't
// care about the packet; resume processing.
return filter . Accept
}
}
var pn tcpip . NetworkProtocolNumber
var pn tcpip . NetworkProtocolNumber
@ -754,7 +803,7 @@ func (ns *Impl) inject() {
}
}
if debugPackets {
if debugPackets {
ns . logf ( "[v2] packet Write out: % x" , stack . PayloadSince ( pkt . NetworkHeader ( ) ) )
ns . logf ( "[v2] packet Write out: % x" , stack . PayloadSince ( pkt . NetworkHeader ( ) ) .AsSlice ( ) )
}
}
// In the normal case, netstack synthesizes the bytes for
// In the normal case, netstack synthesizes the bytes for
@ -767,25 +816,32 @@ func (ns *Impl) inject() {
// Determine if the packet is from a service IP, in which case it
// Determine if the packet is from a service IP, in which case it
// needs to go back into the machines network (inbound) instead of
// needs to go back into the machines network (inbound) instead of
// out.
// out.
// TODO(tom): Work out a way to avoid parsing packets to determine if
// its from the service IP. Maybe gvisor netstack magic. I
// went through the fields of PacketBuffer, and nop :/
// TODO(tom): Figure out if its safe to modify packet.Parsed to fill in
// TODO(tom): Figure out if its safe to modify packet.Parsed to fill in
// the IP src/dest even if its missing the rest of the pkt.
// the IP src/dest even if its missing the rest of the pkt.
// That way we dont have to do this twitchy-af byte-yeeting.
// That way we dont have to do this twitchy-af byte-yeeting.
if b := pkt . NetworkHeader ( ) . Slice ( ) ; len ( b ) >= 20 { // min ipv4 header
hdr := pkt . Network ( )
switch b [ 0 ] >> 4 { // ip proto field
switch v := hdr . ( type ) {
case 4 :
case header . IPv4 :
if srcIP := netaddr . IPv4 ( b [ 12 ] , b [ 13 ] , b [ 14 ] , b [ 15 ] ) ; serviceIP == srcIP {
srcIP := netip . AddrFrom4 ( v . SourceAddress ( ) . As4 ( ) )
if serviceIP == srcIP {
sendToHost = true
}
case header . IPv6 :
srcIP := netip . AddrFrom16 ( v . SourceAddress ( ) . As16 ( ) )
if srcIP == serviceIPv6 {
sendToHost = true
} else if viaRange . Contains ( srcIP ) {
// Only send to the host if this 4via6 route is
// something this node handles.
if ns . lb != nil && ns . lb . ShouldHandleViaIP ( srcIP ) {
sendToHost = true
sendToHost = true
}
if debugNetstack ( ) {
case 6 :
ns . logf ( "netstack: sending 4via6 packet to host: %v" , srcIP )
if len ( b ) >= 40 { // min ipv6 header
if srcIP , ok := netip . AddrFromSlice ( net . IP ( b [ 8 : 24 ] ) ) ; ok && serviceIPv6 == srcIP {
sendToHost = true
}
}
}
}
}
}
default :
// unknown; don't forward to host
}
}
// pkt has a non-zero refcount, so injection methods takes
// pkt has a non-zero refcount, so injection methods takes
@ -868,6 +924,17 @@ var userPingSem = syncs.NewSemaphore(20) // 20 child ping processes at once
var isSynology = runtime . GOOS == "linux" && distro . Get ( ) == distro . Synology
var isSynology = runtime . GOOS == "linux" && distro . Get ( ) == distro . Synology
type userPingDirection int
const (
// userPingDirectionOutbound is used when the pong packet is to be sent
// "outbound"– i.e. from this node to a peer via WireGuard.
userPingDirectionOutbound userPingDirection = iota
// userPingDirectionInbound is used when the pong packet is to be sent
// "inbound"– i.e. from Tailscale to another process on this host.
userPingDirectionInbound
)
// userPing tried to ping dstIP and if it succeeds, injects pingResPkt
// userPing tried to ping dstIP and if it succeeds, injects pingResPkt
// into the tundev.
// into the tundev.
//
//
@ -878,9 +945,13 @@ var isSynology = runtime.GOOS == "linux" && distro.Get() == distro.Synology
// people only use ping occasionally to see if their internet's working
// people only use ping occasionally to see if their internet's working
// so this doesn't need to be great.
// so this doesn't need to be great.
//
//
// The 'direction' parameter is used to determine where the response "pong"
// packet should be written, if the ping succeeds. See the documentation on the
// constants for more details.
//
// TODO(bradfitz): when we're running on Windows as the system user, use
// TODO(bradfitz): when we're running on Windows as the system user, use
// raw socket APIs instead of ping child processes.
// raw socket APIs instead of ping child processes.
func ( ns * Impl ) userPing ( dstIP netip . Addr , pingResPkt [ ] byte ) {
func ( ns * Impl ) userPing ( dstIP netip . Addr , pingResPkt [ ] byte , direction userPingDirection ) {
if ! userPingSem . TryAcquire ( ) {
if ! userPingSem . TryAcquire ( ) {
return
return
}
}
@ -941,8 +1012,14 @@ func (ns *Impl) userPing(dstIP netip.Addr, pingResPkt []byte) {
if debugNetstack ( ) {
if debugNetstack ( ) {
ns . logf ( "exec pinged %v in %v" , dstIP , time . Since ( t0 ) )
ns . logf ( "exec pinged %v in %v" , dstIP , time . Since ( t0 ) )
}
}
if err := ns . tundev . InjectOutbound ( pingResPkt ) ; err != nil {
if direction == userPingDirectionOutbound {
ns . logf ( "InjectOutbound ping response: %v" , err )
if err := ns . tundev . InjectOutbound ( pingResPkt ) ; err != nil {
ns . logf ( "InjectOutbound ping response: %v" , err )
}
} else if direction == userPingDirectionInbound {
if err := ns . tundev . InjectInboundCopy ( pingResPkt ) ; err != nil {
ns . logf ( "InjectInboundCopy ping response: %v" , err )
}
}
}
}
}
@ -977,7 +1054,7 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
h . ToResponse ( )
h . ToResponse ( )
pong = packet . Generate ( & h , p . Payload ( ) )
pong = packet . Generate ( & h , p . Payload ( ) )
}
}
go ns . userPing ( pingIP , pong )
go ns . userPing ( pingIP , pong , userPingDirectionOutbound )
return filter . DropSilently
return filter . DropSilently
}
}