iptables.go raw

   1  // Copyright 2019 The gVisor Authors.
   2  //
   3  // Licensed under the Apache License, Version 2.0 (the "License");
   4  // you may not use this file except in compliance with the License.
   5  // You may obtain a copy of the License at
   6  //
   7  //     http://www.apache.org/licenses/LICENSE-2.0
   8  //
   9  // Unless required by applicable law or agreed to in writing, software
  10  // distributed under the License is distributed on an "AS IS" BASIS,
  11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12  // See the License for the specific language governing permissions and
  13  // limitations under the License.
  14  
  15  package stack
  16  
  17  import (
  18  	"context"
  19  	"fmt"
  20  	"math/rand"
  21  	"reflect"
  22  	"time"
  23  
  24  	"gvisor.dev/gvisor/pkg/tcpip"
  25  	"gvisor.dev/gvisor/pkg/tcpip/header"
  26  )
  27  
  28  // TableID identifies a specific table.
  29  type TableID int
  30  
  31  // Each value identifies a specific table.
  32  const (
  33  	NATID TableID = iota
  34  	MangleID
  35  	FilterID
  36  	NumTables
  37  )
  38  
  39  // HookUnset indicates that there is no hook set for an entrypoint or
  40  // underflow.
  41  const HookUnset = -1
  42  
  43  // reaperDelay is how long to wait before starting to reap connections.
  44  const reaperDelay = 5 * time.Second
  45  
  46  // DefaultTables returns a default set of tables. Each chain is set to accept
  47  // all packets.
  48  func DefaultTables(clock tcpip.Clock, rand *rand.Rand) *IPTables {
  49  	return &IPTables{
  50  		v4Tables: [NumTables]Table{
  51  			NATID: {
  52  				Rules: []Rule{
  53  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  54  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  55  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  56  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  57  					{Filter: EmptyFilter4(), Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  58  				},
  59  				BuiltinChains: [NumHooks]int{
  60  					Prerouting:  0,
  61  					Input:       1,
  62  					Forward:     HookUnset,
  63  					Output:      2,
  64  					Postrouting: 3,
  65  				},
  66  				Underflows: [NumHooks]int{
  67  					Prerouting:  0,
  68  					Input:       1,
  69  					Forward:     HookUnset,
  70  					Output:      2,
  71  					Postrouting: 3,
  72  				},
  73  			},
  74  			MangleID: {
  75  				Rules: []Rule{
  76  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  77  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  78  					{Filter: EmptyFilter4(), Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  79  				},
  80  				BuiltinChains: [NumHooks]int{
  81  					Prerouting: 0,
  82  					Output:     1,
  83  				},
  84  				Underflows: [NumHooks]int{
  85  					Prerouting:  0,
  86  					Input:       HookUnset,
  87  					Forward:     HookUnset,
  88  					Output:      1,
  89  					Postrouting: HookUnset,
  90  				},
  91  			},
  92  			FilterID: {
  93  				Rules: []Rule{
  94  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  95  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  96  					{Filter: EmptyFilter4(), Target: &AcceptTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  97  					{Filter: EmptyFilter4(), Target: &ErrorTarget{NetworkProtocol: header.IPv4ProtocolNumber}},
  98  				},
  99  				BuiltinChains: [NumHooks]int{
 100  					Prerouting:  HookUnset,
 101  					Input:       0,
 102  					Forward:     1,
 103  					Output:      2,
 104  					Postrouting: HookUnset,
 105  				},
 106  				Underflows: [NumHooks]int{
 107  					Prerouting:  HookUnset,
 108  					Input:       0,
 109  					Forward:     1,
 110  					Output:      2,
 111  					Postrouting: HookUnset,
 112  				},
 113  			},
 114  		},
 115  		v6Tables: [NumTables]Table{
 116  			NATID: {
 117  				Rules: []Rule{
 118  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 119  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 120  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 121  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 122  					{Filter: EmptyFilter6(), Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 123  				},
 124  				BuiltinChains: [NumHooks]int{
 125  					Prerouting:  0,
 126  					Input:       1,
 127  					Forward:     HookUnset,
 128  					Output:      2,
 129  					Postrouting: 3,
 130  				},
 131  				Underflows: [NumHooks]int{
 132  					Prerouting:  0,
 133  					Input:       1,
 134  					Forward:     HookUnset,
 135  					Output:      2,
 136  					Postrouting: 3,
 137  				},
 138  			},
 139  			MangleID: {
 140  				Rules: []Rule{
 141  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 142  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 143  					{Filter: EmptyFilter6(), Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 144  				},
 145  				BuiltinChains: [NumHooks]int{
 146  					Prerouting: 0,
 147  					Output:     1,
 148  				},
 149  				Underflows: [NumHooks]int{
 150  					Prerouting:  0,
 151  					Input:       HookUnset,
 152  					Forward:     HookUnset,
 153  					Output:      1,
 154  					Postrouting: HookUnset,
 155  				},
 156  			},
 157  			FilterID: {
 158  				Rules: []Rule{
 159  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 160  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 161  					{Filter: EmptyFilter6(), Target: &AcceptTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 162  					{Filter: EmptyFilter6(), Target: &ErrorTarget{NetworkProtocol: header.IPv6ProtocolNumber}},
 163  				},
 164  				BuiltinChains: [NumHooks]int{
 165  					Prerouting:  HookUnset,
 166  					Input:       0,
 167  					Forward:     1,
 168  					Output:      2,
 169  					Postrouting: HookUnset,
 170  				},
 171  				Underflows: [NumHooks]int{
 172  					Prerouting:  HookUnset,
 173  					Input:       0,
 174  					Forward:     1,
 175  					Output:      2,
 176  					Postrouting: HookUnset,
 177  				},
 178  			},
 179  		},
 180  		connections: ConnTrack{
 181  			seed:  rand.Uint32(),
 182  			clock: clock,
 183  			rand:  rand,
 184  		},
 185  	}
 186  }
 187  
 188  // EmptyFilterTable returns a Table with no rules and the filter table chains
 189  // mapped to HookUnset.
 190  func EmptyFilterTable() Table {
 191  	return Table{
 192  		Rules: []Rule{},
 193  		BuiltinChains: [NumHooks]int{
 194  			Prerouting:  HookUnset,
 195  			Postrouting: HookUnset,
 196  		},
 197  		Underflows: [NumHooks]int{
 198  			Prerouting:  HookUnset,
 199  			Postrouting: HookUnset,
 200  		},
 201  	}
 202  }
 203  
 204  // EmptyNATTable returns a Table with no rules and the filter table chains
 205  // mapped to HookUnset.
 206  func EmptyNATTable() Table {
 207  	return Table{
 208  		Rules: []Rule{},
 209  		BuiltinChains: [NumHooks]int{
 210  			Forward: HookUnset,
 211  		},
 212  		Underflows: [NumHooks]int{
 213  			Forward: HookUnset,
 214  		},
 215  	}
 216  }
 217  
 218  // GetTable returns a table with the given id and IP version. It panics when an
 219  // invalid id is provided.
 220  func (it *IPTables) GetTable(id TableID, ipv6 bool) Table {
 221  	it.mu.RLock()
 222  	defer it.mu.RUnlock()
 223  	return it.getTableRLocked(id, ipv6)
 224  }
 225  
 226  // +checklocksread:it.mu
 227  func (it *IPTables) getTableRLocked(id TableID, ipv6 bool) Table {
 228  	if ipv6 {
 229  		return it.v6Tables[id]
 230  	}
 231  	return it.v4Tables[id]
 232  }
 233  
 234  // ReplaceTable replaces or inserts table by name. It panics when an invalid id
 235  // is provided.
 236  func (it *IPTables) ReplaceTable(id TableID, table Table, ipv6 bool) {
 237  	it.replaceTable(id, table, ipv6, false /* force */)
 238  }
 239  
 240  // ForceReplaceTable replaces or inserts table by name. It panics when an invalid id
 241  // is provided. It enables iptables even when the inserted table is all
 242  // conditionless ACCEPT, skipping our optimization that disables iptables until
 243  // they're modified.
 244  func (it *IPTables) ForceReplaceTable(id TableID, table Table, ipv6 bool) {
 245  	it.replaceTable(id, table, ipv6, true /* force */)
 246  }
 247  
 248  func (it *IPTables) replaceTable(id TableID, table Table, ipv6, force bool) {
 249  	it.mu.Lock()
 250  	defer it.mu.Unlock()
 251  
 252  	// If iptables is being enabled, initialize the conntrack table and
 253  	// reaper.
 254  	if !it.modified {
 255  		// Don't do anything if the table is identical.
 256  		if ((ipv6 && reflect.DeepEqual(table, it.v6Tables[id])) || (!ipv6 && reflect.DeepEqual(table, it.v4Tables[id]))) && !force {
 257  			return
 258  		}
 259  
 260  		it.connections.init()
 261  		it.startReaper(reaperDelay)
 262  	}
 263  	it.modified = true
 264  	if ipv6 {
 265  		it.v6Tables[id] = table
 266  	} else {
 267  		it.v4Tables[id] = table
 268  	}
 269  }
 270  
 271  // A chainVerdict is what a table decides should be done with a packet.
 272  type chainVerdict int
 273  
 274  const (
 275  	// chainAccept indicates the packet should continue through netstack.
 276  	chainAccept chainVerdict = iota
 277  
 278  	// chainDrop indicates the packet should be dropped.
 279  	chainDrop
 280  
 281  	// chainReturn indicates the packet should return to the calling chain
 282  	// or the underflow rule of a builtin chain.
 283  	chainReturn
 284  )
 285  
 286  type checkTable struct {
 287  	fn      checkTableFn
 288  	tableID TableID
 289  	table   Table
 290  }
 291  
 292  // shouldSkipOrPopulateTables returns true iff IPTables should be skipped.
 293  //
 294  // If IPTables should not be skipped, tables will be updated with the
 295  // specified table.
 296  //
 297  // This is called in the hot path even when iptables are disabled, so we ensure
 298  // it does not allocate. We check recursively for heap allocations, but not for:
 299  //   - Stack splitting, which can allocate.
 300  //   - Calls to interfaces, which can allocate.
 301  //   - Calls to dynamic functions, which can allocate.
 302  //
 303  // +checkescape:hard
 304  func (it *IPTables) shouldSkipOrPopulateTables(tables []checkTable, pkt *PacketBuffer) bool {
 305  	switch pkt.NetworkProtocolNumber {
 306  	case header.IPv4ProtocolNumber, header.IPv6ProtocolNumber:
 307  	default:
 308  		// IPTables only supports IPv4/IPv6.
 309  		return true
 310  	}
 311  
 312  	it.mu.RLock()
 313  	defer it.mu.RUnlock()
 314  
 315  	if !it.modified {
 316  		// Many users never configure iptables. Spare them the cost of rule
 317  		// traversal if rules have never been set.
 318  		return true
 319  	}
 320  
 321  	for i := range tables {
 322  		table := &tables[i]
 323  		table.table = it.getTableRLocked(table.tableID, pkt.NetworkProtocolNumber == header.IPv6ProtocolNumber)
 324  	}
 325  	return false
 326  }
 327  
 328  // CheckPrerouting performs the prerouting hook on the packet.
 329  //
 330  // Returns true iff the packet may continue traversing the stack; the packet
 331  // must be dropped if false is returned.
 332  //
 333  // Precondition: The packet's network and transport header must be set.
 334  //
 335  // This is called in the hot path even when iptables are disabled, so we ensure
 336  // that it does not allocate. Note that called functions (e.g.
 337  // getConnAndUpdate) can allocate.
 338  // +checkescape
 339  func (it *IPTables) CheckPrerouting(pkt *PacketBuffer, addressEP AddressableEndpoint, inNicName string) bool {
 340  	tables := [...]checkTable{ // escapes: on arm this causes an allocation.
 341  		{
 342  			fn:      check,
 343  			tableID: MangleID,
 344  		},
 345  		{
 346  			fn:      checkNAT,
 347  			tableID: NATID,
 348  		},
 349  	}
 350  
 351  	if it.shouldSkipOrPopulateTables(tables[:], pkt) {
 352  		return true
 353  	}
 354  
 355  	pkt.tuple = it.connections.getConnAndUpdate(pkt, false /* skipChecksumValidation */)
 356  
 357  	for _, table := range tables {
 358  		if !table.fn(it, table.table, Prerouting, pkt, nil /* route */, addressEP, inNicName, "" /* outNicName */) {
 359  			return false
 360  		}
 361  	}
 362  
 363  	return true
 364  }
 365  
 366  // CheckInput performs the input hook on the packet.
 367  //
 368  // Returns true iff the packet may continue traversing the stack; the packet
 369  // must be dropped if false is returned.
 370  //
 371  // Precondition: The packet's network and transport header must be set.
 372  //
 373  // This is called in the hot path even when iptables are disabled, so we ensure
 374  // that it does not allocate. Note that called functions (e.g.
 375  // getConnAndUpdate) can allocate.
 376  // +checkescape
 377  func (it *IPTables) CheckInput(pkt *PacketBuffer, inNicName string) bool {
 378  	tables := [...]checkTable{ // escapes: on arm this causes an allocation.
 379  		{
 380  			fn:      checkNAT,
 381  			tableID: NATID,
 382  		},
 383  		{
 384  			fn:      check,
 385  			tableID: FilterID,
 386  		},
 387  	}
 388  
 389  	if it.shouldSkipOrPopulateTables(tables[:], pkt) {
 390  		return true
 391  	}
 392  
 393  	for _, table := range tables {
 394  		if !table.fn(it, table.table, Input, pkt, nil /* route */, nil /* addressEP */, inNicName, "" /* outNicName */) {
 395  			return false
 396  		}
 397  	}
 398  
 399  	if t := pkt.tuple; t != nil {
 400  		pkt.tuple = nil
 401  		return t.conn.finalize()
 402  	}
 403  	return true
 404  }
 405  
 406  // CheckForward performs the forward hook on the packet.
 407  //
 408  // Returns true iff the packet may continue traversing the stack; the packet
 409  // must be dropped if false is returned.
 410  //
 411  // Precondition: The packet's network and transport header must be set.
 412  //
 413  // This is called in the hot path even when iptables are disabled, so we ensure
 414  // that it does not allocate. Note that called functions (e.g.
 415  // getConnAndUpdate) can allocate.
 416  // +checkescape
 417  func (it *IPTables) CheckForward(pkt *PacketBuffer, inNicName, outNicName string) bool {
 418  	tables := [...]checkTable{ // escapes: on arm this causes an allocation.
 419  		{
 420  			fn:      check,
 421  			tableID: FilterID,
 422  		},
 423  	}
 424  
 425  	if it.shouldSkipOrPopulateTables(tables[:], pkt) {
 426  		return true
 427  	}
 428  
 429  	for _, table := range tables {
 430  		if !table.fn(it, table.table, Forward, pkt, nil /* route */, nil /* addressEP */, inNicName, outNicName) {
 431  			return false
 432  		}
 433  	}
 434  
 435  	return true
 436  }
 437  
 438  // CheckOutput performs the output hook on the packet.
 439  //
 440  // Returns true iff the packet may continue traversing the stack; the packet
 441  // must be dropped if false is returned.
 442  //
 443  // Precondition: The packet's network and transport header must be set.
 444  //
 445  // This is called in the hot path even when iptables are disabled, so we ensure
 446  // that it does not allocate. Note that called functions (e.g.
 447  // getConnAndUpdate) can allocate.
 448  // +checkescape
 449  func (it *IPTables) CheckOutput(pkt *PacketBuffer, r *Route, outNicName string) bool {
 450  	tables := [...]checkTable{ // escapes: on arm this causes an allocation.
 451  		{
 452  			fn:      check,
 453  			tableID: MangleID,
 454  		},
 455  		{
 456  			fn:      checkNAT,
 457  			tableID: NATID,
 458  		},
 459  		{
 460  			fn:      check,
 461  			tableID: FilterID,
 462  		},
 463  	}
 464  
 465  	if it.shouldSkipOrPopulateTables(tables[:], pkt) {
 466  		return true
 467  	}
 468  
 469  	// We don't need to validate the checksum in the Output path: we can assume
 470  	// we calculate it correctly, plus checksumming may be deferred due to GSO.
 471  	pkt.tuple = it.connections.getConnAndUpdate(pkt, true /* skipChecksumValidation */)
 472  
 473  	for _, table := range tables {
 474  		if !table.fn(it, table.table, Output, pkt, r, nil /* addressEP */, "" /* inNicName */, outNicName) {
 475  			return false
 476  		}
 477  	}
 478  
 479  	return true
 480  }
 481  
 482  // CheckPostrouting performs the postrouting hook on the packet.
 483  //
 484  // Returns true iff the packet may continue traversing the stack; the packet
 485  // must be dropped if false is returned.
 486  //
 487  // Precondition: The packet's network and transport header must be set.
 488  //
 489  // This is called in the hot path even when iptables are disabled, so we ensure
 490  // that it does not allocate. Note that called functions (e.g.
 491  // getConnAndUpdate) can allocate.
 492  // +checkescape
 493  func (it *IPTables) CheckPostrouting(pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, outNicName string) bool {
 494  	tables := [...]checkTable{ // escapes: on arm this causes an allocation.
 495  		{
 496  			fn:      check,
 497  			tableID: MangleID,
 498  		},
 499  		{
 500  			fn:      checkNAT,
 501  			tableID: NATID,
 502  		},
 503  	}
 504  
 505  	if it.shouldSkipOrPopulateTables(tables[:], pkt) {
 506  		return true
 507  	}
 508  
 509  	for _, table := range tables {
 510  		if !table.fn(it, table.table, Postrouting, pkt, r, addressEP, "" /* inNicName */, outNicName) {
 511  			return false
 512  		}
 513  	}
 514  
 515  	if t := pkt.tuple; t != nil {
 516  		pkt.tuple = nil
 517  		return t.conn.finalize()
 518  	}
 519  	return true
 520  }
 521  
 522  // Note: this used to omit the *IPTables parameter, but doing so caused
 523  // unnecessary allocations.
 524  type checkTableFn func(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool
 525  
 526  func checkNAT(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
 527  	return it.checkNAT(table, hook, pkt, r, addressEP, inNicName, outNicName)
 528  }
 529  
 530  // checkNAT runs the packet through the NAT table.
 531  //
 532  // See check.
 533  func (it *IPTables) checkNAT(table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
 534  	t := pkt.tuple
 535  	if t != nil && t.conn.handlePacket(pkt, hook, r) {
 536  		return true
 537  	}
 538  
 539  	if !it.check(table, hook, pkt, r, addressEP, inNicName, outNicName) {
 540  		return false
 541  	}
 542  
 543  	if t == nil {
 544  		return true
 545  	}
 546  
 547  	dnat, natDone := func() (bool, bool) {
 548  		switch hook {
 549  		case Prerouting, Output:
 550  			return true, pkt.dnatDone
 551  		case Input, Postrouting:
 552  			return false, pkt.snatDone
 553  		case Forward:
 554  			panic("should not attempt NAT in forwarding")
 555  		default:
 556  			panic(fmt.Sprintf("unhandled hook = %d", hook))
 557  		}
 558  	}()
 559  
 560  	// Make sure the connection is NATed.
 561  	//
 562  	// If the packet was already NATed, the connection must be NATed.
 563  	if !natDone {
 564  		t.conn.maybePerformNoopNAT(pkt, hook, r, dnat)
 565  	}
 566  
 567  	return true
 568  }
 569  
 570  func check(it *IPTables, table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
 571  	return it.check(table, hook, pkt, r, addressEP, inNicName, outNicName)
 572  }
 573  
 574  // check runs the packet through the rules in the specified table for the
 575  // hook. It returns true if the packet should continue to traverse through the
 576  // network stack or tables, or false when it must be dropped.
 577  //
 578  // Precondition: The packet's network and transport header must be set.
 579  func (it *IPTables) check(table Table, hook Hook, pkt *PacketBuffer, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) bool {
 580  	ruleIdx := table.BuiltinChains[hook]
 581  	switch verdict := it.checkChain(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName); verdict {
 582  	// If the table returns Accept, move on to the next table.
 583  	case chainAccept:
 584  		return true
 585  	// The Drop verdict is final.
 586  	case chainDrop:
 587  		return false
 588  	case chainReturn:
 589  		// Any Return from a built-in chain means we have to
 590  		// call the underflow.
 591  		underflow := table.Rules[table.Underflows[hook]]
 592  		switch v, _ := underflow.Target.Action(pkt, hook, r, addressEP); v {
 593  		case RuleAccept:
 594  			return true
 595  		case RuleDrop:
 596  			return false
 597  		case RuleJump, RuleReturn:
 598  			panic("Underflows should only return RuleAccept or RuleDrop.")
 599  		default:
 600  			panic(fmt.Sprintf("Unknown verdict: %d", v))
 601  		}
 602  	default:
 603  		panic(fmt.Sprintf("Unknown verdict %v.", verdict))
 604  	}
 605  }
 606  
 607  // beforeSave is invoked by stateify.
 608  func (it *IPTables) beforeSave() {
 609  	// Ensure the reaper exits cleanly.
 610  	it.reaper.Stop()
 611  	// Prevent others from modifying the connection table.
 612  	it.connections.mu.Lock()
 613  }
 614  
 615  // afterLoad is invoked by stateify.
 616  func (it *IPTables) afterLoad(context.Context) {
 617  	it.startReaper(reaperDelay)
 618  }
 619  
 620  // startReaper periodically reaps timed out connections.
 621  func (it *IPTables) startReaper(interval time.Duration) {
 622  	bucket := 0
 623  	it.reaper = it.connections.clock.AfterFunc(interval, func() {
 624  		bucket, interval = it.connections.reapUnused(bucket, interval)
 625  		it.reaper.Reset(interval)
 626  	})
 627  }
 628  
 629  // Preconditions:
 630  //   - pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
 631  //   - pkt.NetworkHeader is not nil.
 632  func (it *IPTables) checkChain(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) chainVerdict {
 633  	// Start from ruleIdx and walk the list of rules until a rule gives us
 634  	// a verdict.
 635  	for ruleIdx < len(table.Rules) {
 636  		switch verdict, jumpTo := it.checkRule(hook, pkt, table, ruleIdx, r, addressEP, inNicName, outNicName); verdict {
 637  		case RuleAccept:
 638  			return chainAccept
 639  
 640  		case RuleDrop:
 641  			return chainDrop
 642  
 643  		case RuleReturn:
 644  			return chainReturn
 645  
 646  		case RuleJump:
 647  			// "Jumping" to the next rule just means we're
 648  			// continuing on down the list.
 649  			if jumpTo == ruleIdx+1 {
 650  				ruleIdx++
 651  				continue
 652  			}
 653  			switch verdict := it.checkChain(hook, pkt, table, jumpTo, r, addressEP, inNicName, outNicName); verdict {
 654  			case chainAccept:
 655  				return chainAccept
 656  			case chainDrop:
 657  				return chainDrop
 658  			case chainReturn:
 659  				ruleIdx++
 660  				continue
 661  			default:
 662  				panic(fmt.Sprintf("Unknown verdict: %d", verdict))
 663  			}
 664  
 665  		default:
 666  			panic(fmt.Sprintf("Unknown verdict: %d", verdict))
 667  		}
 668  
 669  	}
 670  
 671  	// We got through the entire table without a decision. Default to DROP
 672  	// for safety.
 673  	return chainDrop
 674  }
 675  
 676  // Preconditions:
 677  //   - pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
 678  //   - pkt.NetworkHeader is not nil.
 679  //
 680  // * pkt is a IPv4 packet of at least length header.IPv4MinimumSize.
 681  // * pkt.NetworkHeader is not nil.
 682  func (it *IPTables) checkRule(hook Hook, pkt *PacketBuffer, table Table, ruleIdx int, r *Route, addressEP AddressableEndpoint, inNicName, outNicName string) (RuleVerdict, int) {
 683  	rule := table.Rules[ruleIdx]
 684  
 685  	// Check whether the packet matches the IP header filter.
 686  	if !rule.Filter.match(pkt, hook, inNicName, outNicName) {
 687  		// Continue on to the next rule.
 688  		return RuleJump, ruleIdx + 1
 689  	}
 690  
 691  	// Go through each rule matcher. If they all match, run
 692  	// the rule target.
 693  	for _, matcher := range rule.Matchers {
 694  		matches, hotdrop := matcher.Match(hook, pkt, inNicName, outNicName)
 695  		if hotdrop {
 696  			return RuleDrop, 0
 697  		}
 698  		if !matches {
 699  			// Continue on to the next rule.
 700  			return RuleJump, ruleIdx + 1
 701  		}
 702  	}
 703  
 704  	// All the matchers matched, so run the target.
 705  	return rule.Target.Action(pkt, hook, r, addressEP)
 706  }
 707  
 708  // OriginalDst returns the original destination of redirected connections. It
 709  // returns an error if the connection doesn't exist or isn't redirected.
 710  func (it *IPTables) OriginalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
 711  	it.mu.RLock()
 712  	defer it.mu.RUnlock()
 713  	if !it.modified {
 714  		return tcpip.Address{}, 0, &tcpip.ErrNotConnected{}
 715  	}
 716  	return it.connections.originalDst(epID, netProto, transProto)
 717  }
 718