diff --git a/stack/port_tcp.go b/stack/port_tcp.go index 6820714..04e5068 100644 --- a/stack/port_tcp.go +++ b/stack/port_tcp.go @@ -20,7 +20,7 @@ type tcphandler func(response []byte, pkt *TCPPacket) (int, error) type tcpPort struct { LastRx time.Time handler tcphandler - Port uint16 + port uint16 packets [1]TCPPacket } @@ -39,6 +39,8 @@ func (p *TCPPacket) String() string { return "TCP Packet: " + p.Eth.String() + " " + p.IP.String() + " " + p.TCP.String() + " payload:" + strconv.Quote(string(p.Payload())) } +func (p tcpPort) Port() uint16 { return p.port } + // NeedsHandling returns true if the socket needs handling before it can // admit more pending packets. func (u *tcpPort) NeedsHandling() bool { @@ -50,14 +52,14 @@ func (u *tcpPort) NeedsHandling() bool { // IsPendingHandling returns true if there are packet(s) pending handling. func (u *tcpPort) IsPendingHandling() bool { - return u.Port != 0 && !u.packets[0].Rx.IsZero() + return u.port != 0 && !u.packets[0].Rx.IsZero() } // HandleEth writes the socket's response into dst to be sent over an ethernet interface. // HandleEth can return 0 bytes written and a nil error to indicate no action must be taken. func (u *tcpPort) HandleEth(dst []byte) (n int, err error) { if u.handler == nil { - panic("nil tcp handler on port " + strconv.Itoa(int(u.Port))) + panic("nil tcp handler on port " + strconv.Itoa(int(u.port))) } packet := &u.packets[0] @@ -73,10 +75,10 @@ func (u *tcpPort) HandleEth(dst []byte) (n int, err error) { // Open sets the UDP handler and opens the port. func (u *tcpPort) Open(port uint16, handler tcphandler) { if port == 0 || handler == nil { - panic("invalid port or nil handler" + strconv.Itoa(int(u.Port))) + panic("invalid port or nil handler" + strconv.Itoa(int(u.port))) } u.handler = handler - u.Port = port + u.port = port for i := range u.packets { u.packets[i].Rx = time.Time{} // Invalidate packets. } @@ -93,7 +95,7 @@ func (s *tcpPort) pending() (p uint32) { func (u *tcpPort) Close() { u.handler = nil - u.Port = 0 // Port 0 flags the port is inactive. + u.port = 0 // Port 0 flags the port is inactive. } func (u *tcpPort) forceResponse() (added bool) { diff --git a/stack/port_udp.go b/stack/port_udp.go index a483d8a..2ba61a7 100644 --- a/stack/port_udp.go +++ b/stack/port_udp.go @@ -12,7 +12,7 @@ type udphandler func(response []byte, pkt *UDPPacket) (int, error) type udpPort struct { LastRx time.Time handler udphandler - Port uint16 + port uint16 packets [1]UDPPacket } @@ -24,6 +24,8 @@ type UDPPacket struct { payload [_MTU - eth.SizeEthernetHeader - eth.SizeIPv4Header - eth.SizeUDPHeader]byte } +func (u udpPort) Port() uint16 { return u.port } + // NeedsHandling returns true if the socket needs handling before it can // admit more pending packets. func (u *udpPort) NeedsHandling() bool { @@ -35,14 +37,14 @@ func (u *udpPort) NeedsHandling() bool { // IsPendingHandling returns true if there are packet(s) pending handling. func (u *udpPort) IsPendingHandling() bool { - return u.Port != 0 && !u.packets[0].Rx.IsZero() + return u.port != 0 && !u.packets[0].Rx.IsZero() } // HandleEth writes the socket's response into dst to be sent over an ethernet interface. // HandleEth can return 0 bytes written and a nil error to indicate no action must be taken. func (u *udpPort) HandleEth(dst []byte) (int, error) { if u.handler == nil { - panic("nil udp handler on port " + strconv.Itoa(int(u.Port))) + panic("nil udp handler on port " + strconv.Itoa(int(u.port))) } packet := &u.packets[0] @@ -58,10 +60,10 @@ func (u *udpPort) HandleEth(dst []byte) (int, error) { // Open sets the UDP handler and opens the port. func (u *udpPort) Open(port uint16, h udphandler) { if port == 0 || h == nil { - panic("invalid port or nil handler" + strconv.Itoa(int(u.Port))) + panic("invalid port or nil handler" + strconv.Itoa(int(u.port))) } u.handler = h - u.Port = port + u.port = port } func (s *udpPort) pending() (p int) { @@ -74,7 +76,7 @@ func (s *udpPort) pending() (p int) { } func (u *udpPort) Close() { - u.Port = 0 // Port 0 flags the port is inactive. + u.port = 0 // Port 0 flags the port is inactive. for i := range u.packets { u.packets[i].Rx = time.Time{} // Invalidate packets. } diff --git a/stack/portstack.go b/stack/portstack.go index ba23f10..411072d 100644 --- a/stack/portstack.go +++ b/stack/portstack.go @@ -21,8 +21,8 @@ const ( ) type PortStackConfig struct { - MAC [6]byte - IP netip.Addr + MAC [6]byte + // IP netip.Addr MaxOpenPortsUDP int MaxOpenPortsTCP int Logger *slog.Logger @@ -32,9 +32,9 @@ type PortStackConfig struct { func NewPortStack(cfg PortStackConfig) *PortStack { var s PortStack s.mac = cfg.MAC - s.IP = cfg.IP - s.UDPv4 = make([]udpPort, cfg.MaxOpenPortsUDP) - s.TCPv4 = make([]tcpPort, cfg.MaxOpenPortsTCP) + // s.ip = cfg.IP.As4() + s.portsUDP = make([]udpPort, cfg.MaxOpenPortsUDP) + s.portsTCP = make([]tcpPort, cfg.MaxOpenPortsTCP) s.logger = cfg.Logger return &s } @@ -75,11 +75,11 @@ type PortStack struct { lastRxSuccess time.Time lastTx time.Time mac [6]byte - // Set IP to non-nil to ignore packets not meant for us. - IP netip.Addr - UDPv4 []udpPort - TCPv4 []tcpPort - GlobalHandler func([]byte) + // Set ip to non-nil to ignore packets not meant for us. + ip [4]byte + portsUDP []udpPort + portsTCP []tcpPort + glob func([]byte) pendingUDPv4 uint32 pendingTCPv4 uint32 droppedPackets uint32 @@ -96,7 +96,6 @@ var ( errPacketExceedsMTU = errors.New("packet exceeds MTU") errNotIPv4 = errors.New("require IPv4") errPacketSmol = errors.New("packet too small") - errNoSocketAvail = errors.New("no available socket") errTooShortTCPOrUDP = errors.New("packet too short to be TCP/UDP") errZeroPort = errors.New("zero port in TCP/UDP") errBadTCPOffset = errors.New("invalid TCP offset") @@ -105,8 +104,20 @@ var ( errBadUDPLength = errors.New("invalid UDP length") errInvalidIHL = errors.New("invalid IP IHL") errIPVersion = errors.New("IP version not supported") + + errPortNoSpace = errors.New("port limit reached") + errPortNoneAvail = errors.New("port unavailable") + errPortNonexistent = errors.New("port nonexistent") ) +func (ps *PortStack) Addr() netip.Addr { return netip.AddrFrom4(ps.ip) } +func (ps *PortStack) SetAddr(addr netip.Addr) { + if !addr.Is4() { + panic("SetAddr only supports IPv4, or argument is not an IP address") + } + ps.ip = addr.As4() +} + func (ps *PortStack) MAC() net.HardwareAddr { return slices.Clone(ps.mac[:]) } func (ps *PortStack) MACAs6() [6]byte { return ps.mac } @@ -123,8 +134,8 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { ps.error("Stack.RecvEth", slog.String("err", err.Error()), slog.Any("IP", ihdr)) } else { ps.lastRxSuccess = ps.lastRx - if ps.GlobalHandler != nil { - ps.GlobalHandler(ethernetFrame) + if ps.glob != nil { + ps.glob(ethernetFrame) } } }() @@ -151,7 +162,7 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { } switch ahdr.Operation { case 1: // ARP request. - if ps.pendingReplyToARP() || ahdr.ProtoTarget != ps.IP.As4() { + if ps.pendingReplyToARP() || ahdr.ProtoTarget != ps.ip { return nil // ARP reply pending or not for us. } // We need to respond to this ARP request. @@ -160,7 +171,7 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { ps.pendingARPresponse = ahdr case 2: // ARP reply. - if ps.ARPresult.Operation != arpOpWait || ahdr.ProtoSender != ps.IP.As4() || + if ps.ARPresult.Operation != arpOpWait || ahdr.ProtoSender != ps.ip || ahdr.ProtoTarget != ps.ARPresult.ProtoTarget { return nil // Result already received | not for us | does not correspond to last request. } @@ -182,7 +193,7 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { case ipOffset < eth.SizeIPv4Header: return errInvalidIHL - case ps.IP.Compare(netip.AddrFrom4(ihdr.Destination)) != 0: + case ps.ip != ihdr.Destination: return nil // Not for us. case uint16(offset) > end || int(offset) > len(payload) || int(end) > len(payload): return errors.New("bad IP TotalLength/IHL") @@ -195,7 +206,7 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { case 17: // UDP (User Datagram Protocol). - if len(ps.UDPv4) == 0 { + if len(ps.portsUDP) == 0 { return nil // No sockets. } else if len(payload) < eth.SizeUDPHeader { return errTooShortTCPOrUDP @@ -214,10 +225,10 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { return errChecksumTCPorUDP } - socket := ps.getUDP(uhdr.DestinationPort) - if socket == nil { + port := findPort(ps.portsUDP, uhdr.DestinationPort) + if port == nil { break // No socket listening on this port. - } else if socket.NeedsHandling() { + } else if port.NeedsHandling() { ps.error("UDP packet dropped") ps.droppedPackets++ return ErrDroppedPacket // Our socket needs handling before admitting more packets. @@ -226,20 +237,20 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { ps.info("UDP packet stored", slog.Int("plen", len(payload))) // Flag packets as needing processing. ps.pendingUDPv4++ - socket.LastRx = ps.lastRx // set as unhandled here. + port.LastRx = ps.lastRx // set as unhandled here. - socket.packets[0].Rx = ps.lastRx - socket.packets[0].Eth = ehdr - socket.packets[0].IP = ihdr - socket.packets[0].UDP = uhdr + port.packets[0].Rx = ps.lastRx + port.packets[0].Eth = ehdr + port.packets[0].IP = ihdr + port.packets[0].UDP = uhdr - copy(socket.packets[0].payload[:], payload) + copy(port.packets[0].payload[:], payload) case 6: ps.info("TCP packet received", slog.Int("plen", len(payload))) // TCP (Transport Control Protocol). switch { - case len(ps.TCPv4) == 0: + case len(ps.portsTCP) == 0: return nil case len(payload) < eth.SizeTCPHeader: return errTooShortTCPOrUDP @@ -259,11 +270,10 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { // return errChecksumTCPorUDP println("bad checksum") } - - socket := ps.getTCP(thdr.DestinationPort) - if socket == nil { + port := findPort(ps.portsTCP, thdr.DestinationPort) + if port == nil { break // No socket listening on this port. - } else if socket.NeedsHandling() { + } else if port.NeedsHandling() { ps.error("TCP packet dropped") ps.droppedPackets++ return ErrDroppedPacket // Our socket needs handling before admitting more packets. @@ -271,15 +281,15 @@ func (ps *PortStack) RecvEth(ethernetFrame []byte) (err error) { ps.info("TCP packet stored", slog.Int("plen", len(payload))) // Flag packets as needing processing. ps.pendingTCPv4++ - socket.LastRx = ps.lastRx // set as unhandled here. - - socket.packets[0].Rx = ps.lastRx - socket.packets[0].Eth = ehdr - socket.packets[0].IP = ihdr - socket.packets[0].TCP = thdr - n := copy(socket.packets[0].data[:], ipOptions) - n += copy(socket.packets[0].data[n:], tcpOptions) - copy(socket.packets[0].data[n:], payload) + port.LastRx = ps.lastRx // set as unhandled here. + + port.packets[0].Rx = ps.lastRx + port.packets[0].Eth = ehdr + port.packets[0].IP = ihdr + port.packets[0].TCP = thdr + n := copy(port.packets[0].data[:], ipOptions) + n += copy(port.packets[0].data[n:], tcpOptions) + copy(port.packets[0].data[n:], payload) } return nil } @@ -354,8 +364,8 @@ func (ps *PortStack) HandleEth(dst []byte) (n int, err error) { } if ps.pendingUDPv4 > 0 { - for i := range ps.UDPv4 { - n, pending, err := handleSocket(dst, &ps.UDPv4[i]) + for i := range ps.portsUDP { + n, pending, err := handleSocket(dst, &ps.portsUDP[i]) if !pending { ps.pendingUDPv4-- } @@ -369,8 +379,8 @@ func (ps *PortStack) HandleEth(dst []byte) (n int, err error) { } if ps.pendingTCPv4 > 0 { - for i := range ps.TCPv4 { - n, pending, err := handleSocket(dst, &ps.TCPv4[i]) + for i := range ps.portsTCP { + n, pending, err := handleSocket(dst, &ps.portsTCP[i]) if !pending { ps.pendingTCPv4-- } @@ -396,7 +406,7 @@ func (ps *PortStack) BeginResolveARPv4(target [4]byte) { HardwareLength: 6, ProtoLength: 4, HardwareSender: ps.MACAs6(), - ProtoSender: ps.IP.As4(), + ProtoSender: ps.ip, HardwareTarget: [6]byte{}, // Zeroes, is filled by target. ProtoTarget: target, } @@ -420,138 +430,108 @@ func (ps *PortStack) IsPendingHandling() bool { return ps.pendingUDPv4 > 0 || ps.pendingTCPv4 > 0 || ps.pendingRequestARP() || ps.pendingReplyToARP() } -// OpenUDP opens a UDP port and sets the handler. If the port is already open +// OpenUDP opens a UDP port and sets the handler. +// OpenUDP returns an error if the port is already open // or if there is no socket available it returns an error. -func (ps *PortStack) OpenUDP(port uint16, handler func([]byte, *UDPPacket) (int, error)) error { +// +// See [PortStack] for information on handler argument. +func (ps *PortStack) OpenUDP(portNum uint16, handler func([]byte, *UDPPacket) (int, error)) error { switch { - case port == 0: + case portNum == 0: return errZeroPort case handler == nil: return errNilHandler } - availIdx := -1 - socketList := ps.UDPv4 - for i := range socketList { - socket := &socketList[i] - if socket.Port == port { - availIdx = -1 - break - } else if availIdx == -1 && socket.Port == 0 { - availIdx = i - } - } - if availIdx == -1 { - return errNoSocketAvail + + port, err := findAvailPort(ps.portsUDP, portNum) + if err != nil { + return err } - socketList[availIdx].Open(port, handler) + port.Open(portNum, handler) return nil } -// FlagUDPPending flags the socket listening on a given port as having a pending -// packet. This is useful to force a response even if no packet has been received. -func (s *PortStack) FlagUDPPending(port uint16) error { - if port == 0 { +// FlagPendingUDP flags a given UDP port as having a pending packet. +// This is useful to force a response even if no packet has been received. +// +// See [PortStack] for more information on how packets are processed. +func (ps *PortStack) FlagPendingUDP(portNum uint16) error { + if portNum == 0 { return errZeroPort } - socket := s.getUDP(port) - if socket == nil { - return errNoSocketAvail + port := findPort(ps.portsUDP, portNum) + if port == nil { + return errPortNonexistent } - if socket.forceResponse() { - s.pendingUDPv4++ + if port.forceResponse() { + ps.pendingUDPv4++ } return nil } -// CloseUDP closes a UDP socket. -func (ps *PortStack) CloseUDP(port uint16) error { - if port == 0 { +// CloseUDP closes a UDP port. See [PortStack]. +func (ps *PortStack) CloseUDP(portNum uint16) error { + if portNum == 0 { return errZeroPort } - socket := ps.getUDP(port) - if socket == nil { - return errNoSocketAvail - } - ps.pendingUDPv4 -= uint32(socket.pending()) - socket.Close() - return nil -} - -func (s *PortStack) getUDP(port uint16) *udpPort { - for i := range s.UDPv4 { - socket := &s.UDPv4[i] - if socket.Port == port { - return socket - } + port := findPort(ps.portsUDP, portNum) + if port == nil { + return errPortNonexistent } + ps.pendingUDPv4 -= uint32(port.pending()) + port.Close() return nil } -// OpenTCP opens a TCP port and sets the handler. If the port is already open +// OpenTCP opens a TCP port and sets the handler. +// OpenTCP returns an error if the port is already open // or if there is no socket available it returns an error. -func (ps *PortStack) OpenTCP(port uint16, handler tcphandler) error { +// +// See [PortStack] for information on handler argument. +func (ps *PortStack) OpenTCP(portNum uint16, handler tcphandler) error { switch { - case port == 0: + case portNum == 0: return errZeroPort case handler == nil: return errNilHandler } - - availIdx := -1 - socketList := ps.TCPv4 - for i := range socketList { - socket := &socketList[i] - if socket.Port == port { - availIdx = -1 - break - } else if availIdx == -1 && socket.Port == 0 { - availIdx = i - } - } - if availIdx == -1 { - return errNoSocketAvail + p, err := findAvailPort(ps.portsTCP, portNum) + if err != nil { + return err } - socketList[availIdx].Open(port, handler) + p.Open(portNum, handler) return nil } -// FlagTCPPending flags the socket listening on a given port as having a pending -// packet. This is useful to force a response even if no packet has been received. -func (ps *PortStack) FlagTCPPending(port uint16) error { - if port == 0 { +// FlagPendingTCP flags a given TCP port as having a pending packet. +// This is useful to force a response even if no packet has been received. +// +// See [PortStack] for more information on how packets are processed. +func (ps *PortStack) FlagPendingTCP(portNum uint16) error { + if portNum == 0 { return errZeroPort } - socket := ps.getTCP(port) - if socket == nil { - return errNoSocketAvail + port := findPort(ps.portsTCP, portNum) + if port == nil { + return errPortNonexistent } - if socket.forceResponse() { + if port.forceResponse() { ps.pendingTCPv4++ } return nil } -// CloseTCP closes a TCP socket. -func (ps *PortStack) CloseTCP(port uint16) error { - if port == 0 { +// CloseTCP closes the TCP port, effectively aborting the connection. See [PortStack]. +func (ps *PortStack) CloseTCP(portNum uint16) error { + if portNum == 0 { return errZeroPort } - socket := ps.getTCP(port) - if socket == nil { - return errNoSocketAvail - } - ps.pendingTCPv4 -= socket.pending() - socket.Close() - return nil -} - -func (ps *PortStack) getTCP(port uint16) *tcpPort { - for i := range ps.TCPv4 { - socket := &ps.TCPv4[i] - if socket.Port == port { - return socket - } + port := findPort(ps.portsTCP, portNum) + if port == nil { + return errPortNonexistent } + ps.pendingTCPv4 -= port.pending() + port.Close() return nil } @@ -597,3 +577,40 @@ func logAttrsPrint(level slog.Level, msg string, attrs ...slog.Attr) { } println() } + +var _ porter = udpPort{} +var _ porter = tcpPort{} + +type porter interface { + Port() uint16 +} + +func findPort[T porter](list []T, port uint16) *T { + for i := range list { + if list[i].Port() == port { + return &list[i] + } + } + return nil +} + +func findAvailPort[T porter](list []T, port uint16) (*T, error) { + availableIdx := -1 + for i := range list { + got := list[i].Port() + if got == port { + availableIdx = -2 + break + } else if got == 0 { // Port==0 means port is unused. + availableIdx = i + break + } + } + switch availableIdx { + case -1: + return nil, errPortNoSpace + case -2: + return nil, errPortNoneAvail + } + return &list[availableIdx], nil +} diff --git a/stack/ring.go b/stack/ring.go index fb1149e..4ae1baf 100644 --- a/stack/ring.go +++ b/stack/ring.go @@ -1,7 +1,6 @@ package stack import ( - "cmp" "errors" "io" ) @@ -95,14 +94,14 @@ func (r *ring) onReadEnd() { } } -func max[T cmp.Ordered](a, b T) T { +func max(a, b int) int { if a > b { return a } return b } -func min[T cmp.Ordered](a, b T) T { +func min(a, b int) int { if a < b { return a } diff --git a/stack/socket_tcp.go b/stack/socket_tcp.go index 05843a0..678b715 100644 --- a/stack/socket_tcp.go +++ b/stack/socket_tcp.go @@ -37,7 +37,7 @@ func (t *TCPSocket) PortStack() *PortStack { } func (t *TCPSocket) AddrPort() netip.AddrPort { - return netip.AddrPortFrom(t.stack.IP, t.localPort) + return netip.AddrPortFrom(t.stack.Addr(), t.localPort) } func (t *TCPSocket) MAC() net.HardwareAddr { @@ -63,7 +63,7 @@ func (t *TCPSocket) Send(b []byte) error { buf: make([]byte, max(defaultSocketSize, len(b))), } } - err := t.stack.FlagTCPPending(t.localPort) + err := t.stack.FlagPendingTCP(t.localPort) if err != nil { return err } @@ -133,7 +133,7 @@ func (t *TCPSocket) Close() error { } } t.closing = true - t.stack.FlagTCPPending(t.localPort) + t.stack.FlagPendingTCP(t.localPort) return nil } @@ -232,7 +232,7 @@ func (t *TCPSocket) handleSend(response []byte, pkt *TCPPacket) (n int, err erro func (t *TCPSocket) setSrcDest(pkt *TCPPacket) { pkt.Eth.Source = t.stack.MACAs6() - pkt.IP.Source = t.stack.IP.As4() + pkt.IP.Source = t.stack.ip pkt.TCP.SourcePort = t.localPort pkt.IP.Destination = t.remote.Addr().As4() diff --git a/stack/stack_test.go b/stack/stack_test.go index 1fb4edc..979f04a 100644 --- a/stack/stack_test.go +++ b/stack/stack_test.go @@ -23,7 +23,7 @@ func TestARP(t *testing.T) { target := stacks[1] const expectedARP = eth.SizeEthernetHeader + eth.SizeARPv4Header // Send ARP request from sender to target. - sender.BeginResolveARPv4(target.IP.As4()) + sender.BeginResolveARPv4(target.Addr().As4()) ex, n := exchangeStacks(t, 1, stacks...) if n != expectedARP { t.Errorf("ex[%d] sent=%d want=%d", ex, n, expectedARP) @@ -41,8 +41,8 @@ func TestARP(t *testing.T) { if result.HardwareTarget != target.MACAs6() { t.Errorf("result.HardwareTarget=%s want=%s", result.HardwareTarget, target.MACAs6()) } - if result.ProtoTarget != target.IP.As4() { - t.Errorf("result.ProtoTarget=%s want=%s", result.ProtoTarget, target.IP.As4()) + if result.ProtoTarget != target.Addr().As4() { + t.Errorf("result.ProtoTarget=%s want=%s", result.ProtoTarget, target.Addr().As4()) } // No more data to exchange. @@ -275,7 +275,7 @@ func createTCPClientServerPair(t *testing.T) (client, server *stack.TCPSocket) { serverStack := stacks[1] // Configure server - serverIP := netip.AddrPortFrom(serverStack.IP, serverPort) + serverIP := netip.AddrPortFrom(serverStack.Addr(), serverPort) serverTCP, err := stack.ListenTCP(serverStack, serverIP.Port(), serverISS, serverWND) if err != nil { t.Fatal(err) @@ -286,7 +286,7 @@ func createTCPClientServerPair(t *testing.T) (client, server *stack.TCPSocket) { if err != nil { t.Fatal(err) } - err = clientStack.FlagTCPPending(clientPort) + err = clientStack.FlagPendingTCP(clientPort) if err != nil { t.Fatal(err) } @@ -305,9 +305,9 @@ func createPortStacks(t *testing.T, n int) (stacks []*stack.PortStack) { ip := netip.AddrFrom4([4]byte{192, 168, u8[1], u8[0]}) Stack := stack.NewPortStack(stack.PortStackConfig{ MAC: MAC, - IP: ip, MaxOpenPortsTCP: 1, }) + Stack.SetAddr(ip) stacks = append(stacks, Stack) } return stacks