From 6fd85892f1c8c5378d4405ac44001933efc3b69923cd36842a5333a570908957 Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Fri, 2 May 2025 19:03:53 -0400 Subject: [PATCH] Messy starting point. But it works --- Makefile | 7 +++- ipam.cc | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 120 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 8063cca..71c1a96 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,12 @@ CXXFLAGS+= -std=c++23 LDLIBS+= -lalepha LDFLAGS+= -L Alepha/build-debug -Wl,-rpath,/home/user/proj/ipam/Alepha/build-debug -CPPFLAGS+= -I . +CPPFLAGS+= -I . -g -O0 -all: test +all: test ipam test: ipam ./ipam + +clean: + ${RM} ipam diff --git a/ipam.cc b/ipam.cc index e0b34ce..8d0ffb7 100644 --- a/ipam.cc +++ b/ipam.cc @@ -7,10 +7,13 @@ #include #include +#include namespace asio= boost::asio; namespace ip= asio::ip; +using Alepha::IOStreams::Str; + template< typename= Alepha::Capabilities< Alepha::IOStreams::Streamable > > struct Allocation_core; @@ -27,7 +30,13 @@ struct IP_Address : ip::network_v6 { std::string s; is >> s; + std::cerr << "Network string: " << s << std::endl; + if( not s.contains( '/' ) ) + { + throw std::runtime_error{ "No subnet mask detected. Did you forget a `/`?" }; + } a= ip::make_network_v6( s ); + std::cerr << "Parsed into: `" << a << std::endl; return is; } @@ -36,12 +45,17 @@ struct IP_Address : ip::network_v6 template< typename > struct Allocation_core { - ip::network_v6 network; + IP_Address network; std::string owner; std::string name; std::string purpose; + std::string country; + std::string region; + std::string city; + std::string postal; + std::vector< Allocation > subAllocations() const; std::vector< Allocation > available() const; }; @@ -85,6 +99,10 @@ try { spaces.push_back( { ip::make_network_v6( "2000::/3" ), "IANA", "Global Routing - v6", "The initial global routing table for v6" } ); + spaces.push_back( { ip::make_network_v6( "2600::/8" ), "ARIN", "ARIN v6 RIR Allocation", "Large ARIN block." } ); + + spaces.push_back( { ip::make_network_v6( "2602:f6a8::/36" ), "IMP-NET", "Imperial Network", "Main Imperial Network Allocation." } ); + add_command ( { @@ -100,6 +118,18 @@ try } ); + add_command + ( + { + "quit", + "Exits the IP Address Manager", + []( ... ) + { + ::exit( EXIT_SUCCESS ); + } + } + ); + add_command ( { @@ -107,22 +137,43 @@ try "Describe a network range", []( const std::vector< std::string > &args ) { + if( args.empty() ) throw std::runtime_error{ "Requires one argument." }; if( args.size() != 1 ) throw std::runtime_error{ "Too many arguments." }; const auto &network= boost::lexical_cast< IP_Address >( args.at( 0 ) ); + std::cout << "Parsed: `" << network << "` -- it has a " << network.prefix_length() + << " bit mask." << std::endl; const Allocation *p= nullptr; + std::optional< IP_Address > found; + auto better= [&]( const auto &where ) + { + return not found.has_value() or where.is_subnet_of( found.value() ); + }; + for( const auto &space: spaces ) { - const auto &[ where, owner, name, purpose ]= space; - if( where == network ) + const auto &[ where, owner, name, purpose, country, state, city, postal ]= space; + std::cerr << "Checking " << network << " against: " << where; + if( network == where ) { - p = &space; + std::cerr << " -- Exact match!" << std::endl; + found= where; + p= &space; break; } + if( network.is_subnet_of( where ) and better( where ) ) + { + std::cerr << " -- Matched." << std::endl; + found= where; + p = &space; + } + else std::cerr << " -- No match." << std::endl; } - const auto &[ _, owner, name, purpose ]= *p; + std::cout << network << " was best matched by " << found.value() << std::endl; + + const auto &[ _, owner, name, purpose, country, state, city, postal ]= *p; std::cout << network << " is owned by " << owner << std::endl; std::cout << "It is called `" << name << "`" << std::endl; @@ -131,9 +182,57 @@ try } ); + add_command + ( + { + "subnets", + "Describe a network range's subnets", + []( const std::vector< std::string > &args ) + { + if( args.empty() ) throw std::runtime_error{ "Requires one argument." }; + if( args.size() != 1 ) throw std::runtime_error{ "Too many arguments." }; + + const auto &network= boost::lexical_cast< IP_Address >( args.at( 0 ) ); + std::cout << "Parsed: `" << network << "` -- it has a " << network.prefix_length() + << " bit mask." << std::endl; + + const Allocation *p= nullptr; + + std::optional< IP_Address > found; + auto better= [&]( const auto &where ) + { + return not found.has_value() or where.is_subnet_of( found.value() ); + }; + + for( const auto &space: spaces ) + { + const auto &[ where, owner, name, purpose, country, state, city, postal ]= space; + if( network.is_subnet_of( where ) and better( where ) ) + { + found= where; + p = &space; + } + } + std::cout << network << " was best matched by " << found.value() << std::endl; + + const auto &[ _, owner, name, purpose, country, state, city, postal ]= *p; + + std::cout << network << " is owned by " << owner << std::endl; + std::cout << "It is called `" << name << "`" << std::endl; + std::cout << "It is used for '" << purpose << "'" << std::endl; + + for( const auto &sub: p->subAllocations() ) + { + std::cout << sub.network << ": " << owner << std::endl; + } + } + } + ); + - while( true ) + while( not std::cin.eof() ) + try { std::string line; getline( std::cin, line ); @@ -143,13 +242,22 @@ try auto args= parsed; args.erase( begin( args ) ); + if( command.empty() ) continue; + if( not commands.contains( command ) ) + { + throw std::runtime_error{ Str << "No such command `" << command << '`' }; + } commands.at( command ).run( args ); } + catch( const std::exception &e ) + { + std::cerr << "ERROR: " << e.what() << std::endl; + } return EXIT_SUCCESS; } catch( const std::exception &ex ) { - std::cerr << "Error: " << ex.what() << std::endl; + std::cerr << "FATAL ERROR: " << ex.what() << std::endl; return EXIT_FAILURE; }