diff --git a/go.mod b/go.mod index 05e468a..b7fa5ea 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/free5gc/ike v1.1.1-0.20241014015325-083f89768f43 github.com/free5gc/ngap v1.0.9-0.20240708062829-734d184eed74 github.com/free5gc/sctp v1.0.1 - github.com/free5gc/util v1.0.7-0.20240713162917-350ee8f4af4c + github.com/free5gc/util v1.0.7-0.20241016064137-5698cc626593 github.com/gin-contrib/pprof v1.5.0 github.com/gin-gonic/gin v1.10.0 github.com/google/gopacket v1.1.19 diff --git a/go.sum b/go.sum index f756cb5..c40e416 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,8 @@ github.com/free5gc/openapi v1.0.9-0.20240503143645-eac9f06c2f6b h1:+VcgZq+3apB6X github.com/free5gc/openapi v1.0.9-0.20240503143645-eac9f06c2f6b/go.mod h1:0qRW+H1/Nyzw5tjjvyp+90m+2SOZZefGQC9QV8iPwu8= github.com/free5gc/sctp v1.0.1 h1:g8WDO97r8B9ubkT5Hyk9b4I1fZUOii9Z39gQ2eRaASo= github.com/free5gc/sctp v1.0.1/go.mod h1:7QXfRWCmlkBGD0EIu3qL5o71bslfIakydz4h2QDZdjQ= -github.com/free5gc/util v1.0.7-0.20240713162917-350ee8f4af4c h1:baToZn4hxGKoCm3BWwYlRuZoCQ74cMZUJzg9BVLEdE0= -github.com/free5gc/util v1.0.7-0.20240713162917-350ee8f4af4c/go.mod h1:IHKIBd4OM9rwSJ0fG/hv6pXbVC+Eu4Lcaq++BWkfSsY= +github.com/free5gc/util v1.0.7-0.20241016064137-5698cc626593 h1:Wp32mLckyP25feOmwHBTS32pSkIAjuZgq8iP7njbe/k= +github.com/free5gc/util v1.0.7-0.20241016064137-5698cc626593/go.mod h1:IHKIBd4OM9rwSJ0fG/hv6pXbVC+Eu4Lcaq++BWkfSsY= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/pprof v1.5.0 h1:E/Oy7g+kNw94KfdCy3bZxQFtyDnAX2V7axRS7sNYVrU= diff --git a/internal/context/context.go b/internal/context/context.go index d91f8d4..30b6665 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -26,6 +26,7 @@ import ( "github.com/free5gc/openapi/models" "github.com/free5gc/sctp" "github.com/free5gc/util/idgenerator" + "github.com/free5gc/util/ippool" ) type n3iwf interface { @@ -59,7 +60,8 @@ type N3IWFContext struct { N3IWFCertificate []byte N3IWFPrivateKey *rsa.PrivateKey - UeIPRange *net.IPNet + IPSecInnerIPPool *ippool.IPPool + // TODO: [TWIF] TwifUe may has its own IP address pool /* XFRM interface */ XfrmIfaces sync.Map // map[uint32]*netlink.Link, XfrmIfaceId as key @@ -130,11 +132,11 @@ func NewContext(n3iwf n3iwf) (*N3IWFContext, error) { n.N3IWFCertificate = block.Bytes // UE IP address range - _, ueIPRange, err := net.ParseCIDR(cfg.GetUEIPAddrRange()) + ueIPPool, err := ippool.NewIPPool(cfg.GetUEIPAddrRange()) if err != nil { - return nil, errors.Errorf("Parse CIDR failed: %+v", err) + return nil, errors.Errorf("NewContext(): %+v", err) } - n.UeIPRange = ueIPRange + n.IPSecInnerIPPool = ueIPPool // XFRM related ikeBindIfaceName, err := getInterfaceName(cfg.GetIKEBindAddr()) @@ -461,26 +463,30 @@ func (c *N3IWFContext) GTPConnectionWithUPFStore(upfAddr string, conn *gtpv1.UPl c.GTPConnectionWithUPF.Store(upfAddr, conn) } -func (c *N3IWFContext) NewInternalUEIPAddr(ikeUe *N3IWFIkeUe) net.IP { +func (c *N3IWFContext) NewIPsecInnerUEIP(ikeUe *N3IWFIkeUe) (net.IP, error) { var ueIPAddr net.IP - + var err error cfg := c.Config() ipsecGwAddr := cfg.GetIPSecGatewayAddr() - // TODO: Check number of allocated IP to detect running out of IPs + for { - ueIPAddr = generateRandomIPinRange(c.UeIPRange) - if ueIPAddr != nil { - if ueIPAddr.String() == ipsecGwAddr { - continue - } - _, ok := c.AllocatedUEIPAddress.LoadOrStore(ueIPAddr.String(), ikeUe) - if !ok { - break - } + ueIPAddr, err = c.IPSecInnerIPPool.Allocate(nil) + if err != nil { + return nil, errors.Wrapf(err, "NewIPsecInnerUEIP()") + } + if ueIPAddr.String() == ipsecGwAddr { + continue + } + _, ok := c.AllocatedUEIPAddress.LoadOrStore(ueIPAddr.String(), ikeUe) + if ok { + logger.CtxLog.Warnf("NewIPsecInnerUEIP(): IP(%v) is used by other IkeUE", + ueIPAddr.String()) + } else { + break } } - return ueIPAddr + return ueIPAddr, nil } func (c *N3IWFContext) DeleteInternalUEIPAddr(ipAddr string) { @@ -554,22 +560,3 @@ func (c *N3IWFContext) AMFSelection( } return availableAMF } - -func generateRandomIPinRange(subnet *net.IPNet) net.IP { - ipAddr := make([]byte, 4) - randomNumber := make([]byte, 4) - - _, err := rand.Read(randomNumber) - if err != nil { - logger.CtxLog.Errorf("Generate random number for IP address failed: %+v", err) - return nil - } - - // TODO: elimenate network name, gateway, and broadcast - for i := 0; i < 4; i++ { - alter := randomNumber[i] & (subnet.Mask[i] ^ 255) - ipAddr[i] = subnet.IP[i] + alter - } - - return net.IPv4(ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]) -} diff --git a/internal/context/context_test.go b/internal/context/context_test.go new file mode 100644 index 0000000..da321d3 --- /dev/null +++ b/internal/context/context_test.go @@ -0,0 +1,106 @@ +package context_test + +import ( + "context" + "net" + "sync" + "testing" + + "github.com/stretchr/testify/require" + + n3iwf_context "github.com/free5gc/n3iwf/internal/context" + "github.com/free5gc/n3iwf/pkg/factory" + "github.com/free5gc/util/ippool" +) + +type n3iwfTestApp struct { + cfg *factory.Config + n3iwfCtx *n3iwf_context.N3IWFContext + ctx context.Context + cancel context.CancelFunc + wg *sync.WaitGroup +} + +func (a *n3iwfTestApp) Config() *factory.Config { + return a.cfg +} + +func (a *n3iwfTestApp) Context() *n3iwf_context.N3IWFContext { + return a.n3iwfCtx +} + +func (a *n3iwfTestApp) CancelContext() context.Context { + return a.ctx +} + +func NewN3iwfTestApp(cfg *factory.Config) (*n3iwfTestApp, error) { + var err error + ctx, cancel := context.WithCancel(context.Background()) + + n3iwfApp := &n3iwfTestApp{ + cfg: cfg, + ctx: ctx, + cancel: cancel, + wg: &sync.WaitGroup{}, + } + + n3iwfApp.n3iwfCtx, err = n3iwf_context.NewTestContext(n3iwfApp) + if err != nil { + return nil, err + } + return n3iwfApp, err +} + +func NewTestCfg() *factory.Config { + return &factory.Config{ + Configuration: &factory.Configuration{ + IPSecGatewayAddr: "10.0.0.1", + UEIPAddressRange: "10.0.0.0/24", + }, + } +} + +func TestNewInternalUEIPAddr(t *testing.T) { + cfg := NewTestCfg() + var app *n3iwfTestApp + var err error + var ip, invalidIP, invalidIP2 net.IP + + app, err = NewN3iwfTestApp(cfg) + require.NoError(t, err) + + n3iwfCtx := app.n3iwfCtx + + invalidIP = net.ParseIP("10.0.0.0") + invalidIP2 = net.ParseIP("10.0.0.255") + n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/24") + require.NoError(t, err) + + for i := 1; i <= 253; i++ { + ip, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) + require.NoError(t, err) + require.NotEqual(t, cfg.GetIPSecGatewayAddr(), ip.String()) + require.NotEqual(t, ip, invalidIP) + require.NotEqual(t, ip, invalidIP2) + } + + _, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) + require.Error(t, err) + + n3iwfCtx.AllocatedUEIPAddress = sync.Map{} + + n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/16") + require.NoError(t, err) + + invalidIP2 = net.ParseIP("10.0.255.255") + for i := 1; i <= 65533; i++ { + ip, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) + require.NoError(t, err) + require.NotEqual(t, cfg.GetIPSecGatewayAddr(), ip.String()) + require.NotEqual(t, ip, invalidIP) + require.NotEqual(t, ip, invalidIP2) + } + + _, err = n3iwfCtx.NewIPsecInnerUEIP(&n3iwf_context.N3IWFIkeUe{}) + require.Error(t, err) +} diff --git a/internal/context/ikeue.go b/internal/context/ikeue.go index 9c64f89..ecdb262 100644 --- a/internal/context/ikeue.go +++ b/internal/context/ikeue.go @@ -217,6 +217,11 @@ func (ikeUe *N3IWFIkeUe) Remove() error { n3iwfCtx.DeleteIKESecurityAssociation(ikeUe.N3IWFIKESecurityAssociation.LocalSPI) n3iwfCtx.DeleteInternalUEIPAddr(ikeUe.IPSecInnerIP.String()) + err := n3iwfCtx.IPSecInnerIPPool.Release(net.ParseIP(ikeUe.IPSecInnerIP.String()).To4()) + if err != nil { + return errors.Wrapf(err, "N3IWFIkeUe Remove()") + } + for _, childSA := range ikeUe.N3IWFChildSecurityAssociation { if err := ikeUe.DeleteChildSA(childSA); err != nil { return err diff --git a/internal/ike/handler.go b/internal/ike/handler.go index 6e73d4a..621d7aa 100644 --- a/internal/ike/handler.go +++ b/internal/ike/handler.go @@ -737,7 +737,13 @@ func (s *Server) HandleIKEAUTH( var ueIPAddr, n3iwfIPAddr net.IP if addrRequest { // IP addresses (IPSec) - ueIPAddr = n3iwfCtx.NewInternalUEIPAddr(ikeUE).To4() + var ueIp net.IP + ueIp, err = n3iwfCtx.NewIPsecInnerUEIP(ikeUE) + if err != nil { + ikeLog.Errorf("HandleIKEAUTH(): %v", err) + return + } + ueIPAddr = ueIp.To4() n3iwfIPAddr = net.ParseIP(ipsecGwAddr).To4() responseConfiguration := responseIKEPayload.BuildConfiguration( @@ -745,7 +751,7 @@ func (s *Server) HandleIKEAUTH( responseConfiguration.ConfigurationAttribute.BuildConfigurationAttribute( ike_message.INTERNAL_IP4_ADDRESS, ueIPAddr) responseConfiguration.ConfigurationAttribute.BuildConfigurationAttribute( - ike_message.INTERNAL_IP4_NETMASK, n3iwfCtx.UeIPRange.Mask) + ike_message.INTERNAL_IP4_NETMASK, n3iwfCtx.IPSecInnerIPPool.IPSubnet.Mask) var ipsecInnerIPAddr *net.IPAddr ikeUE.IPSecInnerIP = ueIPAddr @@ -1072,7 +1078,7 @@ func (s *Server) continueCreateChildSA( // Setup XFRM interface for ipsec var linkIPSec netlink.Link n3iwfIPAddr := net.ParseIP(ipsecGwAddr).To4() - n3iwfIPAddrAndSubnet := net.IPNet{IP: n3iwfIPAddr, Mask: n3iwfCtx.UeIPRange.Mask} + n3iwfIPAddrAndSubnet := net.IPNet{IP: n3iwfIPAddr, Mask: n3iwfCtx.IPSecInnerIPPool.IPSubnet.Mask} newXfrmiId += cfg.GetXfrmIfaceId() + n3iwfCtx.XfrmIfaceIdOffsetForUP newXfrmiName := fmt.Sprintf("%s-%d", cfg.GetXfrmIfaceName(), newXfrmiId) @@ -1152,7 +1158,6 @@ func (s *Server) HandleInformational( switch ikePayload.Type() { case ike_message.TypeD: deletePayload = ikePayload.(*ike_message.Delete) - default: ikeLog.Warnf( "Get IKE payload (type %d) in Inoformational message, this payload will not be handled by IKE handler", diff --git a/internal/ike/handler_test.go b/internal/ike/handler_test.go index 164d8a8..1c1c655 100644 --- a/internal/ike/handler_test.go +++ b/internal/ike/handler_test.go @@ -9,6 +9,7 @@ import ( ike_message "github.com/free5gc/ike/message" n3iwf_context "github.com/free5gc/n3iwf/internal/context" "github.com/free5gc/n3iwf/pkg/factory" + "github.com/free5gc/util/ippool" ) func TestRemoveIkeUe(t *testing.T) { @@ -22,8 +23,14 @@ func TestRemoveIkeUe(t *testing.T) { ikeSA := n3iwfCtx.NewIKESecurityAssociation() ikeUe := n3iwfCtx.NewN3iwfIkeUe(ikeSA.LocalSPI) ikeUe.N3IWFIKESecurityAssociation = ikeSA + ikeUe.IPSecInnerIP = net.ParseIP("10.0.0.1") ikeSA.IsUseDPD = false + n3iwfCtx.IPSecInnerIPPool, err = ippool.NewIPPool("10.0.0.0/24") + require.NoError(t, err) + _, err = n3iwfCtx.IPSecInnerIPPool.Allocate(nil) + require.NoError(t, err) + ikeUe.CreateHalfChildSA(1, 123, 1) ikeAuth := &ike_message.SecurityAssociation{} diff --git a/internal/ike/server.go b/internal/ike/server.go index bc97791..fd5ee93 100644 --- a/internal/ike/server.go +++ b/internal/ike/server.go @@ -3,6 +3,7 @@ package ike import ( "bytes" "context" + "encoding/hex" "fmt" "net" "runtime/debug" @@ -173,6 +174,7 @@ func (s *Server) receiver( msgBuf := make([]byte, n) copy(msgBuf, buf) + ikeLog.Tracef("recv from port(%d):\n%s", localAddr.Port, hex.Dump(msgBuf)) // As specified in RFC 7296 section 3.1, the IKE message send from/to UDP port 4500 // should prepend a 4 bytes zero @@ -349,6 +351,9 @@ func constructPacketWithESP(srcIP, dstIP *net.UDPAddr, espPacket []byte) ([]byte } func handleESPPacket(srcIP, dstIP *net.UDPAddr, espPacket []byte) error { + ikeLog := logger.IKELog + ikeLog.Tracef("Handle ESPPacket") + fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) if err != nil { return errors.Errorf("socket error: %v", err) @@ -356,7 +361,7 @@ func handleESPPacket(srcIP, dstIP *net.UDPAddr, espPacket []byte) error { defer func() { if err = syscall.Close(fd); err != nil { - logger.IKELog.Errorf("Close fd error : %v", err) + ikeLog.Errorf("Close fd error : %v", err) } }() diff --git a/pkg/service/init.go b/pkg/service/init.go index 87e5fb3..fae3aea 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -205,7 +205,7 @@ func (a *N3iwfApp) initDefaultXfrmInterface() error { cfg := a.Config() mainLog := logger.MainLog n3iwfIPAddr := net.ParseIP(cfg.GetIPSecGatewayAddr()).To4() - n3iwfIPAddrAndSubnet := net.IPNet{IP: n3iwfIPAddr, Mask: n3iwfCtx.UeIPRange.Mask} + n3iwfIPAddrAndSubnet := net.IPNet{IP: n3iwfIPAddr, Mask: n3iwfCtx.IPSecInnerIPPool.IPSubnet.Mask} newXfrmiName := fmt.Sprintf("%s-default", cfg.GetXfrmIfaceName()) if linkIPSec, err = xfrm.SetupIPsecXfrmi(newXfrmiName, n3iwfCtx.XfrmParentIfaceName, @@ -216,7 +216,7 @@ func (a *N3iwfApp) initDefaultXfrmInterface() error { route := &netlink.Route{ LinkIndex: linkIPSec.Attrs().Index, - Dst: n3iwfCtx.UeIPRange, + Dst: n3iwfCtx.IPSecInnerIPPool.IPSubnet, } if err := netlink.RouteAdd(route); err != nil {