| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658 |
- package cli
- import (
- "context"
- "fmt"
- "net"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "time"
- release "debtik"
- "debtik/internal/auth"
- "debtik/internal/backup"
- "debtik/internal/bridge"
- "debtik/internal/bwtest"
- "debtik/internal/certs"
- "debtik/internal/dhcp"
- "debtik/internal/dns"
- "debtik/internal/dynamicrouting"
- "debtik/internal/firewall"
- "debtik/internal/logging"
- "debtik/internal/network"
- "debtik/internal/reverseproxy"
- "debtik/internal/routing"
- "debtik/internal/sdwan"
- "debtik/internal/services"
- "debtik/internal/system"
- "debtik/internal/vpn"
- "debtik/internal/vrrp"
- )
- type Executor struct {
- authMgr *auth.Manager
- netMgr *network.Manager
- routingMgr *routing.RoutingManager
- dynRoutingMgr *dynamicrouting.Manager
- fwMgr *firewall.FirewallManager
- dhcpMgr *dhcp.Manager
- dhcpServMgr *dhcp.ServerManager
- dnsMgr *dns.Manager
- bridgeMgr *bridge.BridgeManager
- vpnMgr *vpn.Manager
- vrrpMgr *vrrp.Manager
- bwtestMgr *bwtest.Manager
- servicesMgr *services.Manager
- certMgr *certs.CertManager
- proxyMgr *reverseproxy.Manager
- backupMgr *backup.Manager
- sdwanMgr *sdwan.Manager
- }
- func NewExecutor(
- authMgr *auth.Manager,
- netMgr *network.Manager,
- routingMgr *routing.RoutingManager,
- dynRoutingMgr *dynamicrouting.Manager,
- fwMgr *firewall.FirewallManager,
- dhcpMgr *dhcp.Manager,
- dhcpServMgr *dhcp.ServerManager,
- dnsMgr *dns.Manager,
- bridgeMgr *bridge.BridgeManager,
- vpnMgr *vpn.Manager,
- vrrpMgr *vrrp.Manager,
- bwtestMgr *bwtest.Manager,
- servicesMgr *services.Manager,
- certMgr *certs.CertManager,
- proxyMgr *reverseproxy.Manager,
- backupMgr *backup.Manager,
- sdwanMgr *sdwan.Manager,
- ) *Executor {
- if backupMgr == nil {
- backupMgr = backup.NewManager("", "", "", nil)
- }
- if sdwanMgr == nil {
- sdwanMgr = sdwan.NewManager()
- }
- return &Executor{
- authMgr: authMgr,
- netMgr: netMgr,
- routingMgr: routingMgr,
- dynRoutingMgr: dynRoutingMgr,
- fwMgr: fwMgr,
- dhcpMgr: dhcpMgr,
- dhcpServMgr: dhcpServMgr,
- dnsMgr: dnsMgr,
- bridgeMgr: bridgeMgr,
- vpnMgr: vpnMgr,
- vrrpMgr: vrrpMgr,
- bwtestMgr: bwtestMgr,
- servicesMgr: servicesMgr,
- certMgr: certMgr,
- proxyMgr: proxyMgr,
- backupMgr: backupMgr,
- sdwanMgr: sdwanMgr,
- }
- }
- func (e *Executor) Execute(rawCmd string, user *auth.PublicUser) (output string, shouldExit bool) {
- // A malformed command must never take down the router or its tunnels.
- defer func() {
- if recover() != nil {
- logging.Log("cli,error", "Command handler panicked")
- output = ColorRed + "Internal command error; the router is still running.\n" + ColorReset
- shouldExit = false
- }
- }()
- cmd := strings.TrimSpace(rawCmd)
- if cmd == "" {
- return "", false
- }
- cmd = strings.TrimPrefix(cmd, "/")
- parts, err := splitCommand(cmd)
- if err != nil {
- return ColorRed + "Syntax error: " + err.Error() + "\n" + ColorReset, false
- }
- if len(parts) == 0 {
- return "", false
- }
- mainCmd := strings.ToLower(parts[0])
- switch mainCmd {
- case "quit", "exit":
- return ColorYellow + "Connection closed. Goodbye!\n" + ColorReset, true
- case "help", "?":
- return e.showHelp(), false
- case "clear":
- return "\033[2J\033[H", false
- case "ping":
- return e.handlePing(parts[1:]), false
- case "traceroute":
- return e.handleTraceroute(parts[1:]), false
- case "log":
- return e.handleLog(parts[1:]), false
- case "system":
- return e.handleSystem(parts[1:], user), false
- case "interface":
- return e.handleInterface(parts[1:], user), false
- case "ppp":
- return e.handlePPP(parts[1:], user), false
- case "ip":
- return e.handleIP(parts[1:], user), false
- case "routing":
- return e.handleRouting(parts[1:], user), false
- case "tool":
- if user == nil || user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: tools require write privileges\n" + ColorReset, false
- }
- return e.handleTool(parts[1:]), false
- case "user":
- return e.handleUser(parts[1:], user), false
- case "certificate", "certificates":
- return e.handleCertificate(parts[1:], user), false
- case "reverse-proxy", "reverseproxy":
- return e.handleIPReverseProxy(parts[1:], user), false
- case "sdwan", "sd-wan":
- return e.handleIPSDWAN(parts[1:], user), false
- default:
- return fmt.Sprintf(ColorRed+"Unknown command or syntax error: %q. Type 'help' or '?' for command list.\n"+ColorReset, cmd), false
- }
- }
- func parseArgs(parts []string) ([]string, map[string]string) {
- var pos []string
- params := make(map[string]string)
- for _, p := range parts {
- if idx := strings.Index(p, "="); idx != -1 {
- k := strings.ToLower(p[:idx])
- v := p[idx+1:]
- params[k] = v
- } else {
- pos = append(pos, p)
- }
- }
- return pos, params
- }
- // ==================== LOGGING ====================
- func (e *Executor) handleLog(parts []string) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" || parts[0] == "show" {
- limit := 50
- filter := ""
- args := parts
- if len(args) > 0 {
- args = args[1:]
- }
- _, params := parseArgs(args)
- if lStr, ok := params["limit"]; ok {
- if lVal, err := strconv.Atoi(lStr); err == nil && lVal > 0 {
- limit = lVal
- }
- }
- if fStr, ok := params["where"]; ok {
- filter = fStr
- } else if fStr, ok := params["filter"]; ok {
- filter = fStr
- }
- logger := logging.GetLogger()
- entries := logger.List(limit, filter)
- if len(entries) == 0 {
- return ColorYellow + "No log records found.\n" + ColorReset
- }
- tbl := NewTable("#", "TIME", "TOPICS", "MESSAGE")
- for _, logEntry := range entries {
- timeStr := logEntry.Time.Format("Jan/02 15:04:05")
- tbl.AddRow(
- fmt.Sprintf("%d", logEntry.ID),
- timeStr,
- logEntry.Topics,
- logEntry.Message,
- )
- }
- return tbl.String()
- }
- return ColorRed + "Usage: /log print [limit=N] [where=filter]\n" + ColorReset
- }
- // ==================== INTERFACES ====================
- func (e *Executor) handleInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.printAllInterfaces()
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "print", "pr", "show", "list":
- return e.printAllInterfaces()
- case "vlan":
- return e.handleInterfaceVLAN(parts[1:], user)
- case "bridge":
- return e.handleInterfaceBridge(parts[1:], user)
- case "wireguard":
- return e.handleInterfaceWireGuard(parts[1:], user)
- case "aerotun":
- return e.handleInterfaceAeroTUN(parts[1:], user)
- case "eoip":
- return e.handleInterfaceEoIP(parts[1:], user)
- case "vxlan":
- return e.handleInterfaceVXLAN(parts[1:], user)
- case "vrrp":
- return e.handleInterfaceVRRP(parts[1:], user)
- case "pppoe-client":
- return e.handleInterfacePPPoE(parts[1:], user)
- case "l2tp-server":
- return e.handleInterfaceL2TPServer(parts[1:], user)
- case "pptp-server":
- return e.handleInterfacePPTPServer(parts[1:], user)
- case "pppoe-server":
- return e.handleInterfacePPPoEServer(parts[1:], user)
- case "enable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /interface enable <name>\n" + ColorReset
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
- }
- name := parts[1]
- if err := e.netMgr.SetInterfaceState(name, true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Interface %s enabled successfully.\n", name) + ColorReset
- case "disable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /interface disable <name>\n" + ColorReset
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
- }
- name := parts[1]
- if err := e.netMgr.SetInterfaceState(name, false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Interface %s disabled successfully.\n", name) + ColorReset
- case "set":
- if len(parts) < 2 {
- return ColorRed + "Usage: /interface set <name> mtu=<val>\n" + ColorReset
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify interfaces\n" + ColorReset
- }
- name := parts[1]
- _, params := parseArgs(parts[2:])
- if mtuStr, ok := params["mtu"]; ok {
- mtu, err := strconv.Atoi(mtuStr)
- if err != nil || mtu < 68 || mtu > 9000 {
- return ColorRed + "Invalid MTU value (68-9000)\n" + ColorReset
- }
- if err := e.netMgr.SetInterfaceMTU(name, mtu); err != nil {
- return ColorRed + "Error setting MTU: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Interface %s MTU updated to %d.\n", name, mtu) + ColorReset
- }
- return ColorRed + "Usage: /interface set <name> mtu=<val>\n" + ColorReset
- default:
- return ColorRed + "Unknown interface subcommand: " + sub + "\n" + ColorReset
- }
- }
- func (e *Executor) printAllInterfaces() string {
- ifaces, err := e.netMgr.ListInterfaces()
- if err != nil {
- return ColorRed + "Error listing interfaces: " + err.Error() + ColorReset + "\n"
- }
- tbl := NewTable("FLAGS", "NAME", "TYPE", "STATE", "MTU", "MAC ADDRESS", "IP ADDRESSES")
- for _, iface := range ifaces {
- isUp := strings.ToUpper(iface.State) == "UP"
- flags := ""
- if isUp {
- flags += "R"
- } else {
- flags += "X"
- }
- stateStr := ColorGreen + "UP" + ColorReset
- if !isUp {
- stateStr = ColorRed + "DOWN" + ColorReset
- }
- allIPs := append([]string{}, iface.IPv4...)
- allIPs = append(allIPs, iface.IPv6...)
- ips := strings.Join(allIPs, ", ")
- if ips == "" {
- ips = "-"
- }
- tbl.AddRow(flags, iface.Name, string(iface.Type), stateStr, fmt.Sprintf("%d", iface.MTU), iface.MAC, ips)
- }
- return tbl.String()
- }
- // --- VLAN ---
- func (e *Executor) handleInterfaceVLAN(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- vlans, err := e.netMgr.ListVLANs()
- if err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- tbl := NewTable("#", "NAME", "VLAN-ID", "INTERFACE", "MTU", "STATE")
- for idx, v := range vlans {
- isUp := strings.ToUpper(v.State) == "UP"
- st := ColorGreen + "UP" + ColorReset
- if !isUp {
- st = ColorRed + "DOWN" + ColorReset
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), v.Name, fmt.Sprintf("%d", v.VlanID), v.ParentName, fmt.Sprintf("%d", v.MTU), st)
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify VLANs\n" + ColorReset
- }
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- parent := params["interface"]
- vlanIDStr := params["vlan-id"]
- if vlanIDStr == "" {
- vlanIDStr = params["vlan_id"]
- }
- vlanID, err := strconv.Atoi(vlanIDStr)
- if err != nil || vlanID < 1 || vlanID > 4094 {
- return ColorRed + "Invalid vlan-id (1-4094)\n" + ColorReset
- }
- if parent == "" {
- return ColorRed + "Missing parent interface (interface=...)\n" + ColorReset
- }
- if name == "" {
- name = fmt.Sprintf("%s.%d", parent, vlanID)
- }
- req := network.CreateVLANRequest{
- ParentName: parent,
- VlanID: vlanID,
- Name: name,
- AutoUp: true,
- }
- if _, err := e.netMgr.CreateVLAN(req); err != nil {
- return ColorRed + "Error creating VLAN: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VLAN interface %s created.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vlan remove <name>\n" + ColorReset
- }
- name := pos[0]
- if err := e.netMgr.DeleteVLAN(name); err != nil {
- return ColorRed + "Error deleting VLAN: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VLAN %s removed.\n", name) + ColorReset
- default:
- return ColorRed + "Usage: /interface vlan [print|add|remove]\n" + ColorReset
- }
- }
- // --- Bridge ---
- func (e *Executor) handleInterfaceBridge(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- if e.bridgeMgr == nil {
- return ColorRed + "Bridge manager not available.\n" + ColorReset
- }
- bridges := e.bridgeMgr.ListBridges()
- tbl := NewTable("FLAGS", "NAME", "MTU", "MAC-ADDRESS", "RUNNING", "PORTS")
- for _, b := range bridges {
- flags := ""
- if b.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- tbl.AddRow(flags, b.Name, fmt.Sprintf("%d", b.MTU), b.MACAddress, fmt.Sprintf("%t", b.Running), fmt.Sprintf("%d", b.PortsCount))
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- if sub == "port" {
- return e.handleBridgePort(parts[1:], user)
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify bridge\n" + ColorReset
- }
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- return ColorRed + "Usage: /interface bridge add name=<name>\n" + ColorReset
- }
- b := bridge.BridgeInstance{
- Name: name,
- Disabled: false,
- }
- if err := e.bridgeMgr.SaveBridge(b); err != nil {
- return ColorRed + "Error adding bridge: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Bridge %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface bridge remove <id|name>\n" + ColorReset
- }
- if err := e.bridgeMgr.DeleteBridge(pos[0]); err != nil {
- return ColorRed + "Error deleting bridge: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Bridge %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface bridge enable <id|name>\n" + ColorReset
- }
- if err := e.bridgeMgr.SetBridgeState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Bridge %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface bridge disable <id|name>\n" + ColorReset
- }
- if err := e.bridgeMgr.SetBridgeState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Bridge %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface bridge [print|add|remove|enable|disable|port]\n" + ColorReset
- }
- }
- func (e *Executor) handleBridgePort(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ports := e.bridgeMgr.ListPorts()
- tbl := NewTable("FLAGS", "INTERFACE", "BRIDGE", "STATUS", "PVID")
- for _, p := range ports {
- flags := ""
- if p.Disabled {
- flags += "X"
- } else {
- flags += "H"
- }
- tbl.AddRow(flags, p.Interface, p.Bridge, p.Status, fmt.Sprintf("%d", p.PVID))
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify bridge ports\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- br := params["bridge"]
- iface := params["interface"]
- if br == "" || iface == "" {
- return ColorRed + "Usage: /interface bridge port add bridge=<bridge> interface=<iface>\n" + ColorReset
- }
- port := bridge.BridgePort{
- Bridge: br,
- Interface: iface,
- Disabled: false,
- }
- if err := e.bridgeMgr.SavePort(port); err != nil {
- return ColorRed + "Error adding bridge port: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Port %s attached to bridge %s.\n", iface, br) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface bridge port remove <id>\n" + ColorReset
- }
- if err := e.bridgeMgr.DeletePort(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Bridge port %s removed.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface bridge port [print|add|remove]\n" + ColorReset
- }
- }
- // --- WireGuard ---
- func (e *Executor) handleInterfaceWireGuard(parts []string, user *auth.PublicUser) string {
- if e.vpnMgr == nil || e.vpnMgr.WireGuard == nil {
- return ColorRed + "WireGuard subsystem not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.vpnMgr.WireGuard.ListInterfaces()
- tbl := NewTable("FLAGS", "NAME", "LISTEN-PORT", "PUBLIC-KEY", "STATUS")
- for _, w := range ifaces {
- flags := ""
- if w.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- st := "running"
- if !w.Running {
- st = "stopped"
- }
- tbl.AddRow(flags, w.Name, fmt.Sprintf("%d", w.ListenPort), w.PublicKey, st)
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- if sub == "peers" || sub == "peer" {
- return e.handleWireGuardPeers(parts[1:], user)
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify wireguard\n" + ColorReset
- }
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- portStr := params["listen-port"]
- privKey := params["private-key"]
- port := 51820
- if portStr != "" {
- if pVal, err := strconv.Atoi(portStr); err == nil && pVal > 0 {
- port = pVal
- }
- }
- if name == "" {
- return ColorRed + "Usage: /interface wireguard add name=<name> [listen-port=<port>]\n" + ColorReset
- }
- req := vpn.SaveWireGuardInterfaceRequest{
- WireGuardInterface: vpn.WireGuardInterface{
- Name: name,
- ListenPort: port,
- PrivateKey: privKey,
- Disabled: false,
- },
- }
- if err := e.vpnMgr.WireGuard.SaveInterface(req); err != nil {
- return ColorRed + "Error adding WireGuard interface: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("WireGuard interface %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface wireguard remove <name>\n" + ColorReset
- }
- if err := e.vpnMgr.WireGuard.DeleteInterface(pos[0]); err != nil {
- return ColorRed + "Error deleting WireGuard interface: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("WireGuard interface %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface wireguard enable <name>\n" + ColorReset
- }
- if err := e.vpnMgr.WireGuard.SetInterfaceEnabled(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("WireGuard interface %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface wireguard disable <name>\n" + ColorReset
- }
- if err := e.vpnMgr.WireGuard.SetInterfaceEnabled(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("WireGuard interface %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface wireguard [print|add|remove|enable|disable|peers]\n" + ColorReset
- }
- }
- func (e *Executor) handleWireGuardPeers(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- _, params := parseArgs(parts)
- peers := e.vpnMgr.WireGuard.ListPeers(params["interface"])
- tbl := NewTable("INTERFACE", "PUBLIC-KEY", "ENDPOINT", "ALLOWED-IPS", "LAST-HANDSHAKE")
- for _, p := range peers {
- hs := "-"
- if p.LatestHandshake != "" {
- hs = p.LatestHandshake
- }
- ep := p.EndpointAddress
- if p.EndpointPort > 0 {
- ep = fmt.Sprintf("%s:%d", p.EndpointAddress, p.EndpointPort)
- }
- tbl.AddRow(p.Interface, p.PublicKey, ep, strings.Join(p.AllowedIPs, ","), hs)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify wireguard peers\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- iface := params["interface"]
- pubKey := params["public-key"]
- endpoint := params["endpoint"]
- allowed := params["allowed-address"]
- if allowed == "" {
- allowed = params["allowed-ips"]
- }
- if iface == "" || pubKey == "" {
- return ColorRed + "Usage: /interface wireguard peers add interface=<iface> public-key=<key> [endpoint=host:port] [allowed-address=cidr]\n" + ColorReset
- }
- var allowedList []string
- if allowed != "" {
- allowedList = strings.Split(allowed, ",")
- }
- epHost := endpoint
- epPort := 0
- if strings.Contains(endpoint, ":") {
- parts := strings.Split(endpoint, ":")
- epHost = parts[0]
- if p, err := strconv.Atoi(parts[1]); err == nil {
- epPort = p
- }
- }
- req := vpn.SaveWireGuardPeerRequest{
- WireGuardPeer: vpn.WireGuardPeer{
- Interface: iface,
- PublicKey: pubKey,
- EndpointAddress: epHost,
- EndpointPort: epPort,
- AllowedIPs: allowedList,
- Disabled: false,
- },
- }
- if err := e.vpnMgr.WireGuard.SavePeer(req); err != nil {
- return ColorRed + "Error saving WireGuard peer: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "WireGuard peer added successfully.\n" + ColorReset
- case "remove", "rem", "delete":
- _, params := parseArgs(parts[1:])
- iface := params["interface"]
- pubKey := params["public-key"]
- if iface == "" || pubKey == "" {
- return ColorRed + "Usage: /interface wireguard peers remove interface=<iface> public-key=<key>\n" + ColorReset
- }
- if err := e.vpnMgr.WireGuard.DeletePeer(iface, pubKey); err != nil {
- return ColorRed + "Error deleting WireGuard peer: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "WireGuard peer removed successfully.\n" + ColorReset
- default:
- return ColorRed + "Usage: /interface wireguard peers [print|add|remove]\n" + ColorReset
- }
- }
- // --- EoIP ---
- func (e *Executor) handleInterfaceEoIP(parts []string, user *auth.PublicUser) string {
- if e.vpnMgr == nil || e.vpnMgr.EoIP == nil {
- return ColorRed + "EoIP subsystem not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- tunnels := e.vpnMgr.EoIP.List()
- tbl := NewTable("FLAGS", "NAME", "REMOTE-ADDRESS", "TUNNEL-ID", "STATUS")
- for _, t := range tunnels {
- flags := ""
- if t.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- st := "stopped"
- if t.Running {
- st = "running"
- }
- tbl.AddRow(flags, t.Name, t.RemoteAddress, fmt.Sprintf("%d", t.TunnelID), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify EoIP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- remote := params["remote-address"]
- tidStr := params["tunnel-id"]
- tid, err := strconv.Atoi(tidStr)
- if err != nil || tid < 1 || tid > 65535 {
- return ColorRed + "Invalid tunnel-id (1-65535)\n" + ColorReset
- }
- if name == "" || remote == "" {
- return ColorRed + "Usage: /interface eoip add name=<name> remote-address=<ip> tunnel-id=<id>\n" + ColorReset
- }
- inst := &vpn.EoIPInstance{
- Name: name,
- RemoteAddress: remote,
- TunnelID: tid,
- Disabled: false,
- }
- if err := e.vpnMgr.EoIP.Save(inst); err != nil {
- return ColorRed + "Error adding EoIP tunnel: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("EoIP tunnel %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface eoip remove <id>\n" + ColorReset
- }
- if err := e.vpnMgr.EoIP.Delete(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("EoIP tunnel %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface eoip enable <id>\n" + ColorReset
- }
- if err := e.vpnMgr.EoIP.SetState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("EoIP tunnel %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface eoip disable <id>\n" + ColorReset
- }
- if err := e.vpnMgr.EoIP.SetState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("EoIP tunnel %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface eoip [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- // --- VXLAN ---
- func (e *Executor) handleInterfaceVXLAN(parts []string, user *auth.PublicUser) string {
- if e.vpnMgr == nil || e.vpnMgr.VXLAN == nil {
- return ColorRed + "VXLAN subsystem not available.\n" + ColorReset
- }
- if len(parts) > 0 {
- sub := strings.ToLower(parts[0])
- if sub == "vteps" || sub == "vtep" {
- return e.handleInterfaceVXLANVteps(parts[1:], user)
- }
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- tunnels := e.vpnMgr.VXLAN.List()
- tbl := NewTable("FLAGS", "NAME", "VNI", "PORT", "MTU", "MAC-ADDRESS", "STATUS")
- for _, t := range tunnels {
- flags := ""
- if t.Disabled {
- flags += "X"
- }
- if t.Running {
- flags += "R"
- }
- st := "stopped"
- if !t.Disabled {
- if t.Running {
- st = "running"
- } else {
- st = "down"
- }
- }
- tbl.AddRow(flags, t.Name, fmt.Sprintf("%d", t.VNI), fmt.Sprintf("%d", t.Port), fmt.Sprintf("%d", t.MTU), t.MACAddress, st)
- }
- return tbl.String()
- }
- if user != nil && user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify VXLAN\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- vniStr := params["vni"]
- vni, err := strconv.Atoi(vniStr)
- if err != nil || vni < 1 || vni > 16777215 {
- return ColorRed + "Invalid or missing vni (1-16777215)\n" + ColorReset
- }
- name := params["name"]
- if name == "" {
- name = fmt.Sprintf("vxlan%d", vni)
- }
- port := 4789
- if pStr := params["port"]; pStr != "" {
- if p, err := strconv.Atoi(pStr); err == nil && p > 0 && p <= 65535 {
- port = p
- }
- }
- mtu := 1500
- if mStr := params["mtu"]; mStr != "" {
- if m, err := strconv.Atoi(mStr); err == nil && m >= 576 {
- mtu = m
- }
- }
- learning := true
- if lStr := params["learning"]; lStr != "" {
- lLower := strings.ToLower(lStr)
- if lLower == "no" || lLower == "false" || lLower == "0" {
- learning = false
- }
- }
- inst := &vpn.VXLANInstance{
- Name: name,
- VNI: vni,
- Port: port,
- MTU: mtu,
- MACAddress: params["mac-address"],
- IPAddress: params["ip-address"],
- LocalAddress: params["local-address"],
- RemoteAddress: params["remote-address"],
- Group: params["group"],
- ParentInterface: params["interface"],
- Learning: learning,
- Comment: params["comment"],
- Disabled: false,
- }
- if err := e.vpnMgr.VXLAN.Save(inst); err != nil {
- return ColorRed + "Error adding VXLAN interface: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VXLAN interface %s added.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vxlan set <name|id> [name=...] [vni=...] [port=...] [mtu=...] [comment=...]\n" + ColorReset
- }
- inst, found := e.vpnMgr.VXLAN.Get(pos[0])
- if !found {
- return ColorRed + fmt.Sprintf("VXLAN interface %q not found\n", pos[0]) + ColorReset
- }
- if newName := params["name"]; newName != "" {
- inst.Name = newName
- }
- if vniStr := params["vni"]; vniStr != "" {
- if v, err := strconv.Atoi(vniStr); err == nil && v >= 1 && v <= 16777215 {
- inst.VNI = v
- }
- }
- if pStr := params["port"]; pStr != "" {
- if p, err := strconv.Atoi(pStr); err == nil && p > 0 {
- inst.Port = p
- }
- }
- if mStr := params["mtu"]; mStr != "" {
- if m, err := strconv.Atoi(mStr); err == nil && m >= 576 {
- inst.MTU = m
- }
- }
- if mac := params["mac-address"]; mac != "" {
- inst.MACAddress = mac
- }
- if ip := params["ip-address"]; ip != "" {
- inst.IPAddress = ip
- }
- if loc := params["local-address"]; loc != "" {
- inst.LocalAddress = loc
- }
- if rem := params["remote-address"]; rem != "" {
- inst.RemoteAddress = rem
- }
- if grp := params["group"]; grp != "" {
- inst.Group = grp
- }
- if iface := params["interface"]; iface != "" {
- inst.ParentInterface = iface
- }
- if cmt, ok := params["comment"]; ok {
- inst.Comment = cmt
- }
- if err := e.vpnMgr.VXLAN.Save(inst); err != nil {
- return ColorRed + "Error updating VXLAN interface: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VXLAN interface %s updated.\n", inst.Name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vxlan remove <name|id>\n" + ColorReset
- }
- if err := e.vpnMgr.VXLAN.Delete(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VXLAN interface %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vxlan enable <name|id>\n" + ColorReset
- }
- if err := e.vpnMgr.VXLAN.SetState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VXLAN interface %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vxlan disable <name|id>\n" + ColorReset
- }
- if err := e.vpnMgr.VXLAN.SetState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VXLAN interface %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface vxlan [print|add|set|remove|enable|disable|vteps]\n" + ColorReset
- }
- }
- // --- VXLAN VTEPs ---
- func (e *Executor) handleInterfaceVXLANVteps(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- _, params := parseArgs(parts)
- vteps := e.vpnMgr.VXLAN.ListVteps(params["interface"])
- tbl := NewTable("INTERFACE", "REMOTE-IP", "PORT", "COMMENT")
- for _, v := range vteps {
- pStr := fmt.Sprintf("%d", v.Port)
- if v.Port <= 0 {
- pStr = "default"
- }
- tbl.AddRow(v.Interface, v.RemoteIP, pStr, v.Comment)
- }
- return tbl.String()
- }
- if user != nil && user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify VXLAN VTEPs\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- iface := params["interface"]
- remote := params["remote-ip"]
- if remote == "" {
- remote = params["remote-address"]
- }
- if iface == "" || remote == "" {
- return ColorRed + "Usage: /interface vxlan vteps add interface=<iface> remote-ip=<ip> [port=4789] [comment=...]\n" + ColorReset
- }
- port := 0
- if pStr := params["port"]; pStr != "" {
- if p, err := strconv.Atoi(pStr); err == nil && p > 0 {
- port = p
- }
- }
- vtep := &vpn.VXLANVtep{
- Interface: iface,
- RemoteIP: remote,
- Port: port,
- Comment: params["comment"],
- }
- if err := e.vpnMgr.VXLAN.AddVtep(iface, vtep); err != nil {
- return ColorRed + "Error adding VTEP: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VTEP %s added to interface %s.\n", remote, iface) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- iface := params["interface"]
- remote := params["remote-ip"]
- if remote == "" {
- remote = params["remote-address"]
- }
- if iface != "" && remote != "" {
- if err := e.vpnMgr.VXLAN.DeleteVtep(iface, remote); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VTEP %s removed from %s.\n", remote, iface) + ColorReset
- }
- if len(pos) >= 2 {
- if err := e.vpnMgr.VXLAN.DeleteVtep(pos[0], pos[1]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VTEP %s removed from %s.\n", pos[1], pos[0]) + ColorReset
- }
- return ColorRed + "Usage: /interface vxlan vteps remove interface=<iface> remote-ip=<ip> OR <iface> <remote-ip>\n" + ColorReset
- default:
- return ColorRed + "Usage: /interface vxlan vteps [print|add|remove]\n" + ColorReset
- }
- }
- // --- VRRP ---
- func (e *Executor) handleInterfaceVRRP(parts []string, user *auth.PublicUser) string {
- if e.vrrpMgr == nil {
- return ColorRed + "VRRP subsystem not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- instances := e.vrrpMgr.List()
- tbl := NewTable("FLAGS", "NAME", "INTERFACE", "VRID", "PRIORITY", "STATE", "VIP")
- for _, inst := range instances {
- flags := ""
- if inst.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- tbl.AddRow(flags, inst.Name, inst.Interface, fmt.Sprintf("%d", inst.VRID), fmt.Sprintf("%d", inst.Priority), inst.State, strings.Join(inst.VirtualIPs, ","))
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify VRRP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- iface := params["interface"]
- vridStr := params["vrid"]
- prioStr := params["priority"]
- vip := params["vip"]
- if vip == "" {
- vip = params["virtual-ip"]
- }
- vrid, err := strconv.Atoi(vridStr)
- if err != nil || vrid < 1 || vrid > 255 {
- return ColorRed + "Invalid vrid (1-255)\n" + ColorReset
- }
- prio := 100
- if prioStr != "" {
- if pVal, err := strconv.Atoi(prioStr); err == nil && pVal >= 1 && pVal <= 254 {
- prio = pVal
- }
- }
- if name == "" || iface == "" || vip == "" {
- return ColorRed + "Usage: /interface vrrp add name=<name> interface=<iface> vrid=<id> vip=<ip/cidr> [priority=100]\n" + ColorReset
- }
- inst := vrrp.VRRPInstance{
- Name: name,
- Interface: iface,
- VRID: vrid,
- Priority: prio,
- VirtualIPs: []string{vip},
- Disabled: false,
- }
- if _, err := e.vrrpMgr.Save(inst); err != nil {
- return ColorRed + "Error adding VRRP: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VRRP instance %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vrrp remove <id>\n" + ColorReset
- }
- if err := e.vrrpMgr.Delete(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VRRP instance %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vrrp enable <id>\n" + ColorReset
- }
- if err := e.vrrpMgr.ToggleState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VRRP instance %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface vrrp disable <id>\n" + ColorReset
- }
- if err := e.vrrpMgr.ToggleState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("VRRP instance %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface vrrp [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- // --- PPPoE Client ---
- func (e *Executor) handleInterfacePPPoE(parts []string, user *auth.PublicUser) string {
- if e.vpnMgr == nil || e.vpnMgr.PPPoE == nil {
- return ColorRed + "PPPoE subsystem not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- clients := e.vpnMgr.PPPoE.List()
- tbl := NewTable("FLAGS", "NAME", "INTERFACE", "USER", "STATUS", "IP")
- for _, c := range clients {
- flags := ""
- if c.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- st := "stopped"
- if c.Running {
- st = "running"
- }
- tbl.AddRow(flags, c.Name, c.Interface, c.Username, st, c.AssignedIP)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify PPPoE\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- iface := params["interface"]
- usr := params["user"]
- pwd := params["password"]
- if name == "" || iface == "" || usr == "" {
- return ColorRed + "Usage: /interface pppoe-client add name=<name> interface=<iface> user=<usr> password=<pwd>\n" + ColorReset
- }
- inst := vpn.PPPoEInstance{
- Name: name,
- Interface: iface,
- Username: usr,
- Password: pwd,
- Disabled: false,
- }
- if err := e.vpnMgr.PPPoE.Save(inst); err != nil {
- return ColorRed + "Error adding PPPoE client: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("PPPoE client %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface pppoe-client remove <name>\n" + ColorReset
- }
- if err := e.vpnMgr.PPPoE.Delete(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("PPPoE client %s removed.\n", pos[0]) + ColorReset
- case "connect":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface pppoe-client connect <name>\n" + ColorReset
- }
- if err := e.vpnMgr.PPPoE.Connect(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Connecting PPPoE client %s...\n", pos[0]) + ColorReset
- case "disconnect":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /interface pppoe-client disconnect <name>\n" + ColorReset
- }
- if err := e.vpnMgr.PPPoE.Disconnect(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Disconnected PPPoE client %s.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /interface pppoe-client [print|add|remove|connect|disconnect]\n" + ColorReset
- }
- }
- // ==================== IP SUBSYSTEM ====================
- func (e *Executor) handleIP(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return ColorYellow + "IP subsystem. Available: address, route, dhcp-client, dhcp-server, dns, firewall, service\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "address", "addr":
- return e.handleIPAddress(parts[1:], user)
- case "route":
- return e.handleIPRoute(parts[1:], user)
- case "dhcp-client":
- return e.handleIPDHCPClient(parts[1:], user)
- case "dhcp-server":
- return e.handleIPDHCPServer(parts[1:], user)
- case "dns":
- return e.handleIPDNS(parts[1:], user)
- case "firewall":
- return e.handleIPFirewall(parts[1:], user)
- case "service", "services":
- return e.handleIPService(parts[1:], user)
- case "reverse-proxy", "reverseproxy", "proxy":
- return e.handleIPReverseProxy(parts[1:], user)
- case "sdwan", "sd-wan":
- return e.handleIPSDWAN(parts[1:], user)
- default:
- return ColorRed + "Unknown IP command: " + sub + "\n" + ColorReset
- }
- }
- // --- IP Address ---
- func (e *Executor) handleIPAddress(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces, err := e.netMgr.ListInterfaces()
- if err != nil {
- return ColorRed + "Error listing IP addresses: " + err.Error() + ColorReset + "\n"
- }
- tbl := NewTable("#", "ADDRESS", "NETWORK", "INTERFACE", "STATUS")
- idx := 0
- for _, iface := range ifaces {
- for _, cidr := range append(iface.IPv4, iface.IPv6...) {
- ip, ipNet, parseErr := net.ParseCIDR(cidr)
- netStr := "-"
- if parseErr == nil && ipNet != nil {
- netStr = ipNet.IP.String()
- } else if ip != nil {
- netStr = ip.String()
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), cidr, netStr, iface.Name, "enabled")
- idx++
- }
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify IP addresses\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- addr := params["address"]
- iface := params["interface"]
- if addr == "" || iface == "" {
- return ColorRed + "Usage: /ip address add address=<ip/mask> interface=<iface>\n" + ColorReset
- }
- if err := e.netMgr.AddInterfaceIP(iface, addr); err != nil {
- return ColorRed + "Error adding IP address: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("IP address %s added to interface %s.\n", addr, iface) + ColorReset
- case "remove", "rem", "delete":
- _, params := parseArgs(parts[1:])
- addr := params["address"]
- iface := params["interface"]
- if addr == "" || iface == "" {
- return ColorRed + "Usage: /ip address remove address=<ip/mask> interface=<iface>\n" + ColorReset
- }
- if err := e.netMgr.DeleteInterfaceIP(iface, addr); err != nil {
- return ColorRed + "Error removing IP address: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("IP address %s removed from interface %s.\n", addr, iface) + ColorReset
- default:
- return ColorRed + "Usage: /ip address [print|add|remove]\n" + ColorReset
- }
- }
- // --- IP Route ---
- func (e *Executor) handleIPRoute(parts []string, user *auth.PublicUser) string {
- if e.routingMgr == nil {
- return ColorRed + "Routing manager not available\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- routes := e.routingMgr.ListRoutes()
- tbl := NewTable("FLAGS", "DST-ADDRESS", "GATEWAY", "DISTANCE", "TABLE", "STATUS")
- for _, r := range routes {
- flags := ""
- if r.Disabled {
- flags += "X"
- } else {
- flags += "A"
- }
- if r.Static {
- flags += "S"
- } else {
- flags += "D"
- }
- st := "active"
- if r.Disabled {
- st = "disabled"
- }
- tbl.AddRow(flags, r.DstAddress, r.Gateway, fmt.Sprintf("%d", r.Distance), r.RoutingTable, st)
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "rule":
- rules := e.routingMgr.ListPolicyRules()
- tbl := NewTable("#", "SRC", "DST", "TABLE", "PRIORITY")
- for idx, r := range rules {
- tbl.AddRow(fmt.Sprintf("%d", idx), r.SrcAddress, r.DstAddress, r.Table, fmt.Sprintf("%d", r.Priority))
- }
- return tbl.String()
- case "table":
- tables := e.routingMgr.ListTables()
- tbl := NewTable("#", "NAME", "TABLE-ID", "COMMENT")
- for idx, t := range tables {
- tbl.AddRow(fmt.Sprintf("%d", idx), t.Name, fmt.Sprintf("%d", t.TableID), t.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify routes\n" + ColorReset
- }
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- dst := params["dst-address"]
- if dst == "" {
- dst = params["dst"]
- }
- gw := params["gateway"]
- iface := params["interface"]
- if gw == "" && iface != "" {
- gw = iface
- }
- if gw != "" && iface == "" && net.ParseIP(gw) == nil {
- iface = gw
- }
- table := params["routing-table"]
- if table == "" {
- table = "main"
- }
- dist := 1
- if dStr, ok := params["distance"]; ok {
- if dVal, err := strconv.Atoi(dStr); err == nil && dVal >= 1 {
- dist = dVal
- }
- }
- if dst == "" || (gw == "" && iface == "") {
- return ColorRed + "Usage: /ip route add dst-address=<cidr> gateway=<ip|interface> [distance=1] [routing-table=main]\n" + ColorReset
- }
- r := routing.RouteItem{
- DstAddress: dst,
- Gateway: gw,
- Interface: iface,
- Distance: dist,
- RoutingTable: table,
- Static: true,
- Disabled: false,
- }
- if err := e.routingMgr.SaveRoute(r); err != nil {
- return ColorRed + "Error adding route: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Route to %s via %s added.\n", dst, gw) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip route remove <id>\n" + ColorReset
- }
- if err := e.routingMgr.DeleteRoute(pos[0]); err != nil {
- return ColorRed + "Error removing route: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Route %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip route enable <id>\n" + ColorReset
- }
- if err := e.routingMgr.SetRouteState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Route %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip route disable <id>\n" + ColorReset
- }
- if err := e.routingMgr.SetRouteState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Route %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip route [print|add|remove|enable|disable|rule|table]\n" + ColorReset
- }
- }
- // --- IP DHCP Client ---
- func (e *Executor) handleIPDHCPClient(parts []string, user *auth.PublicUser) string {
- if e.dhcpMgr == nil {
- return ColorRed + "DHCP client manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- clients := e.dhcpMgr.ListClients()
- tbl := NewTable("FLAGS", "INTERFACE", "USE-PEER-DNS", "STATUS", "ADDRESS", "GATEWAY")
- for _, c := range clients {
- flags := ""
- if !c.Enabled {
- flags += "X"
- } else {
- flags += "R"
- }
- addr := c.IPAddress
- if addr == "" {
- addr = "-"
- }
- gw := c.Gateway
- if gw == "" {
- gw = "-"
- }
- tbl.AddRow(flags, c.Interface, fmt.Sprintf("%t", c.UsePeerDNS), c.Status, addr, gw)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify DHCP clients\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- iface := params["interface"]
- if iface == "" {
- return ColorRed + "Usage: /ip dhcp-client add interface=<iface> [use-peer-dns=yes]\n" + ColorReset
- }
- peerDNS := true
- if val, ok := params["use-peer-dns"]; ok {
- peerDNS = (strings.ToLower(val) == "yes" || strings.ToLower(val) == "true")
- }
- req := dhcp.CreateDHCPClientRequest{
- Interface: iface,
- UsePeerDNS: peerDNS,
- Enabled: true,
- }
- if _, err := e.dhcpMgr.AddClient(req); err != nil {
- return ColorRed + "Error adding DHCP client: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP client added on interface %s.\n", iface) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-client remove <id>\n" + ColorReset
- }
- if err := e.dhcpMgr.DeleteClient(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP client %s removed.\n", pos[0]) + ColorReset
- case "renew":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-client renew <id>\n" + ColorReset
- }
- if err := e.dhcpMgr.RenewLease(pos[0]); err != nil {
- return ColorRed + "Error renewing lease: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP lease renewal requested for %s.\n", pos[0]) + ColorReset
- case "release":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-client release <id>\n" + ColorReset
- }
- if err := e.dhcpMgr.ReleaseLease(pos[0]); err != nil {
- return ColorRed + "Error releasing lease: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP lease released for %s.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip dhcp-client [print|add|remove|renew|release]\n" + ColorReset
- }
- }
- // --- IP DHCP Server ---
- func (e *Executor) handleIPDHCPServer(parts []string, user *auth.PublicUser) string {
- if e.dhcpServMgr == nil {
- return ColorRed + "DHCP server manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- servers := e.dhcpServMgr.ListServers()
- tbl := NewTable("FLAGS", "NAME", "INTERFACE", "ADDRESS-POOL", "LEASE-TIME")
- for _, s := range servers {
- flags := ""
- if s.Disabled {
- flags += "X"
- } else {
- flags += "R"
- }
- tbl.AddRow(flags, s.Name, s.Interface, s.AddressPool, s.LeaseTime)
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "lease":
- if len(parts) > 1 && (parts[1] == "remove" || parts[1] == "rem" || parts[1] == "delete") {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- pos, _ := parseArgs(parts[2:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-server lease remove <ip|mac|id>\n" + ColorReset
- }
- if err := e.dhcpServMgr.DeleteStaticLease(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Lease %s removed.\n", pos[0]) + ColorReset
- }
- leases := e.dhcpServMgr.ListLeases()
- tbl := NewTable("IP-ADDRESS", "MAC-ADDRESS", "HOSTNAME", "EXPIRES-IN", "STATUS")
- for _, l := range leases {
- tbl.AddRow(l.Address, l.MACAddress, l.HostName, l.ExpiresIn, l.Status)
- }
- return tbl.String()
- case "network":
- if len(parts) > 1 {
- act := strings.ToLower(parts[1])
- if act == "add" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- addr := params["address"]
- if addr == "" {
- addr = params["subnet"]
- }
- if addr == "" {
- return ColorRed + "Usage: /ip dhcp-server network add address=<subnet/cidr> [gateway=<ip>] [dns-server=<ip1,ip2>] [domain=<domain>]\n" + ColorReset
- }
- var dnsServers []string
- if dnsStr, ok := params["dns-server"]; ok && dnsStr != "" {
- dnsServers = strings.Split(dnsStr, ",")
- }
- netw := dhcp.DHCPNetwork{
- Address: addr,
- Gateway: params["gateway"],
- DNSServers: dnsServers,
- Domain: params["domain"],
- }
- if _, err := e.dhcpServMgr.SaveNetwork(netw); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("DHCP network %s added.\n", addr) + ColorReset
- }
- if act == "remove" || act == "rem" || act == "delete" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- pos, _ := parseArgs(parts[2:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-server network remove <#|subnet|id>\n" + ColorReset
- }
- target := pos[0]
- nets := e.dhcpServMgr.ListNetworks()
- var netID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nets) {
- netID = nets[idx].ID
- } else {
- for _, n := range nets {
- if n.Address == target || n.ID == target {
- netID = n.ID
- break
- }
- }
- }
- if netID == "" {
- netID = target
- }
- if err := e.dhcpServMgr.DeleteNetwork(netID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("DHCP network %s removed.\n", target) + ColorReset
- }
- }
- networks := e.dhcpServMgr.ListNetworks()
- tbl := NewTable("#", "SUBNET", "GATEWAY", "DNS-SERVERS", "DOMAIN")
- for idx, n := range networks {
- tbl.AddRow(fmt.Sprintf("%d", idx), n.Address, n.Gateway, strings.Join(n.DNSServers, ","), n.Domain)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify DHCP server\n" + ColorReset
- }
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- iface := params["interface"]
- pool := params["address-pool"]
- if pool == "" {
- start := params["range-start"]
- end := params["range-end"]
- if start != "" && end != "" {
- pool = fmt.Sprintf("%s-%s", start, end)
- }
- }
- leaseTime := params["lease-time"]
- if leaseTime == "" {
- leaseTime = "12h"
- }
- if name == "" || iface == "" || pool == "" {
- return ColorRed + "Usage: /ip dhcp-server add name=<name> interface=<iface> address-pool=<start-end> [lease-time=12h]\n" + ColorReset
- }
- inst := dhcp.DHCPServerInstance{
- Name: name,
- Interface: iface,
- AddressPool: pool,
- LeaseTime: leaseTime,
- Disabled: false,
- }
- if _, err := e.dhcpServMgr.SaveServer(inst); err != nil {
- return ColorRed + "Error adding DHCP server: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP server %s added on interface %s.\n", name, iface) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-server remove <id>\n" + ColorReset
- }
- if err := e.dhcpServMgr.DeleteServer(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP server %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-server enable <id>\n" + ColorReset
- }
- if err := e.dhcpServMgr.ToggleServerState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP server %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dhcp-server disable <id>\n" + ColorReset
- }
- if err := e.dhcpServMgr.ToggleServerState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("DHCP server %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip dhcp-server [print|add|remove|enable|disable|lease|network]\n" + ColorReset
- }
- }
- // --- IP DNS ---
- func (e *Executor) handleIPDNS(parts []string, user *auth.PublicUser) string {
- if e.dnsMgr == nil {
- return ColorRed + "DNS manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- settings := e.dnsMgr.GetSettings()
- out := " servers: " + strings.Join(settings.Servers, ", ") + "\n"
- out += " dynamic-servers: " + strings.Join(settings.DynamicServers, ", ") + "\n"
- out += fmt.Sprintf(" allow-remote-requests: %t\n", settings.AllowRemoteRequests)
- out += fmt.Sprintf(" cache-size: %d entries\n", settings.CacheSize)
- out += fmt.Sprintf(" domain: %s\n", settings.Domain)
- return out
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "cache":
- if len(parts) > 1 && parts[1] == "flush" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- if err := e.dnsMgr.FlushCache(); err != nil {
- return ColorRed + "Error flushing DNS cache: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "DNS cache flushed.\n" + ColorReset
- }
- return ColorYellow + "Usage: /ip dns cache flush\n" + ColorReset
- case "static":
- if len(parts) > 1 {
- act := strings.ToLower(parts[1])
- if act == "add" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- name := params["name"]
- addr := params["address"]
- if name == "" || addr == "" {
- return ColorRed + "Usage: /ip dns static add name=<domain> address=<ip> [ttl=300]\n" + ColorReset
- }
- ttl := 300
- if v, ok := params["ttl"]; ok {
- fmt.Sscanf(v, "%d", &ttl)
- }
- rec := dns.StaticRecord{
- Name: name,
- Address: addr,
- TTL: ttl,
- }
- if err := e.dnsMgr.SaveStaticRecord(rec); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("DNS static record %s -> %s added.\n", name, addr) + ColorReset
- }
- if act == "remove" || act == "rem" || act == "delete" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- pos, _ := parseArgs(parts[2:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dns static remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- recs := e.dnsMgr.ListStaticRecords()
- var recID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(recs) {
- recID = recs[idx].ID
- } else {
- for _, r := range recs {
- if strings.EqualFold(r.Name, target) || r.ID == target {
- recID = r.ID
- break
- }
- }
- }
- if recID == "" {
- recID = target
- }
- if err := e.dnsMgr.DeleteStaticRecord(recID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("DNS static record %s removed.\n", target) + ColorReset
- }
- if act == "enable" || act == "disable" {
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- pos, _ := parseArgs(parts[2:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip dns static " + act + " <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- recs := e.dnsMgr.ListStaticRecords()
- var recID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(recs) {
- recID = recs[idx].ID
- } else {
- for _, r := range recs {
- if strings.EqualFold(r.Name, target) || r.ID == target {
- recID = r.ID
- break
- }
- }
- }
- if recID == "" {
- recID = target
- }
- if err := e.dnsMgr.ToggleStaticRecord(recID, act == "disable"); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("DNS static record %s %sd.\n", target, act) + ColorReset
- }
- }
- records := e.dnsMgr.ListStaticRecords()
- tbl := NewTable("#", "NAME", "ADDRESS", "TTL", "STATUS")
- for idx, r := range records {
- st := "enabled"
- if r.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), r.Name, r.Address, fmt.Sprintf("%d", r.TTL), st)
- }
- return tbl.String()
- case "set":
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- _, params := parseArgs(parts[1:])
- if sStr, ok := params["servers"]; ok {
- srvs := strings.Split(sStr, ",")
- cfg := e.dnsMgr.GetSettings()
- cfg.Servers = srvs
- if err := e.dnsMgr.SaveSettings(cfg); err != nil {
- return ColorRed + "Error saving DNS settings: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "DNS servers updated.\n" + ColorReset
- }
- return ColorRed + "Usage: /ip dns set servers=<ip1,ip2>\n" + ColorReset
- default:
- return ColorRed + "Usage: /ip dns [print|set|cache flush|static]\n" + ColorReset
- }
- }
- // --- IP Firewall ---
- func (e *Executor) handleIPFirewall(parts []string, user *auth.PublicUser) string {
- if e.fwMgr == nil {
- return ColorRed + "Firewall manager not available.\n" + ColorReset
- }
- if len(parts) == 0 {
- return ColorYellow + "Firewall subsystem. Available: filter, nat, mangle, address-list\n" + ColorReset
- }
- tableType := strings.ToLower(parts[0])
- switch tableType {
- case "filter":
- return e.handleFirewallFilter(parts[1:], user)
- case "nat":
- return e.handleFirewallNAT(parts[1:], user)
- case "mangle":
- return e.handleFirewallMangle(parts[1:], user)
- case "address-list":
- return e.handleFirewallAddressList(parts[1:], user)
- case "connection", "connections":
- return e.handleFirewallConnection(parts[1:], user)
- default:
- return ColorRed + "Unknown firewall table: " + tableType + "\n" + ColorReset
- }
- }
- func (e *Executor) handleFirewallConnection(parts []string, user *auth.PublicUser) string {
- data, err := os.ReadFile("/proc/net/nf_conntrack")
- if err != nil {
- out, cErr := exec.Command("conntrack", "-L").CombinedOutput()
- if cErr != nil {
- return ColorYellow + "No active conntrack connections found or nf_conntrack module inactive.\n" + ColorReset
- }
- data = out
- }
- tbl := NewTable("PROTO", "SRC", "DST", "SPORT", "DPORT", "STATE")
- lines := strings.Split(string(data), "\n")
- count := 0
- for _, l := range lines {
- l = strings.TrimSpace(l)
- if l == "" {
- continue
- }
- fields := strings.Fields(l)
- proto := "-"
- src := "-"
- dst := "-"
- sport := "-"
- dport := "-"
- state := "-"
- for _, f := range fields {
- if f == "tcp" || f == "udp" || f == "icmp" {
- proto = f
- } else if strings.HasPrefix(f, "src=") && src == "-" {
- src = strings.TrimPrefix(f, "src=")
- } else if strings.HasPrefix(f, "dst=") && dst == "-" {
- dst = strings.TrimPrefix(f, "dst=")
- } else if strings.HasPrefix(f, "sport=") && sport == "-" {
- sport = strings.TrimPrefix(f, "sport=")
- } else if strings.HasPrefix(f, "dport=") && dport == "-" {
- dport = strings.TrimPrefix(f, "dport=")
- } else if f == "ESTABLISHED" || f == "TIME_WAIT" || f == "CLOSE" || f == "SYN_SENT" || f == "SYN_RECV" {
- state = f
- }
- }
- tbl.AddRow(proto, src, dst, sport, dport, state)
- count++
- if count >= 100 {
- break
- }
- }
- if count == 0 {
- return "No active connection tracking entries.\n"
- }
- return tbl.String()
- }
- func (e *Executor) handleFirewallFilter(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- rules := e.fwMgr.ListFilterRules()
- tbl := NewTable("FLAGS", "CHAIN", "ACTION", "PROTO", "SRC-ADDR", "DST-ADDR", "DST-PORT", "COMMENT")
- for _, r := range rules {
- flags := ""
- if r.Disabled {
- flags += "X"
- } else {
- flags += " "
- }
- tbl.AddRow(flags, r.Chain, r.Action, r.Protocol, r.SrcAddress, r.DstAddress, r.DstPort, r.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify firewall\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- chain := params["chain"]
- action := params["action"]
- if chain == "" || action == "" {
- return ColorRed + "Usage: /ip firewall filter add chain=<input|forward|output> action=<accept|drop|reject> [protocol=...] [dst-port=...]\n" + ColorReset
- }
- r := firewall.FilterRule{
- Chain: chain,
- Action: action,
- Protocol: params["protocol"],
- SrcAddress: params["src-address"],
- DstAddress: params["dst-address"],
- DstPort: params["dst-port"],
- Comment: params["comment"],
- Disabled: false,
- }
- if err := e.fwMgr.SaveFilterRule(r); err != nil {
- return ColorRed + "Error adding filter rule: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "Firewall filter rule added.\n" + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall filter remove <id>\n" + ColorReset
- }
- if err := e.fwMgr.DeleteFilterRule(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Filter rule %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall filter enable <id>\n" + ColorReset
- }
- if err := e.fwMgr.SetFilterRuleState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Filter rule %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall filter disable <id>\n" + ColorReset
- }
- if err := e.fwMgr.SetFilterRuleState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Filter rule %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip firewall filter [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleFirewallNAT(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- rules := e.fwMgr.ListNATRules()
- tbl := NewTable("FLAGS", "CHAIN", "ACTION", "OUT-IFACE", "TO-ADDRESSES", "TO-PORTS", "COMMENT")
- for _, r := range rules {
- flags := ""
- if r.Disabled {
- flags += "X"
- } else {
- flags += " "
- }
- tbl.AddRow(flags, r.Chain, r.Action, r.OutInterface, r.ToAddresses, r.ToPorts, r.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify firewall\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- chain := params["chain"]
- action := params["action"]
- if chain == "" || action == "" {
- return ColorRed + "Usage: /ip firewall nat add chain=<srcnat|dstnat> action=<masquerade|dst-nat|src-nat> [out-interface=...] [to-addresses=...]\n" + ColorReset
- }
- r := firewall.NATRule{
- Chain: chain,
- Action: action,
- OutInterface: params["out-interface"],
- ToAddresses: params["to-addresses"],
- ToPorts: params["to-ports"],
- Comment: params["comment"],
- Disabled: false,
- }
- if err := e.fwMgr.SaveNATRule(r); err != nil {
- return ColorRed + "Error adding NAT rule: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "Firewall NAT rule added.\n" + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall nat remove <id>\n" + ColorReset
- }
- if err := e.fwMgr.DeleteNATRule(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("NAT rule %s removed.\n", pos[0]) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall nat enable <id>\n" + ColorReset
- }
- if err := e.fwMgr.SetNATRuleState(pos[0], false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("NAT rule %s enabled.\n", pos[0]) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall nat disable <id>\n" + ColorReset
- }
- if err := e.fwMgr.SetNATRuleState(pos[0], true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("NAT rule %s disabled.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip firewall nat [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleFirewallMangle(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- rules := e.fwMgr.ListMangleRules()
- tbl := NewTable("FLAGS", "CHAIN", "ACTION", "PASSTHROUGH", "COMMENT")
- for _, r := range rules {
- flags := ""
- if r.Disabled {
- flags += "X"
- } else {
- flags += " "
- }
- tbl.AddRow(flags, r.Chain, r.Action, fmt.Sprintf("%t", r.Passthrough), r.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- chain := params["chain"]
- action := params["action"]
- if chain == "" || action == "" {
- return ColorRed + "Usage: /ip firewall mangle add chain=... action=...\n" + ColorReset
- }
- r := firewall.MangleRule{
- Chain: chain,
- Action: action,
- Passthrough: true,
- Comment: params["comment"],
- Disabled: false,
- }
- if err := e.fwMgr.SaveMangleRule(r); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + "Mangle rule added.\n" + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall mangle remove <id>\n" + ColorReset
- }
- if err := e.fwMgr.DeleteMangleRule(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Mangle rule %s removed.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip firewall mangle [print|add|remove]\n" + ColorReset
- }
- }
- func (e *Executor) handleFirewallAddressList(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- entries := e.fwMgr.ListAddressLists()
- tbl := NewTable("FLAGS", "LIST", "ADDRESS", "COMMENT")
- for _, entry := range entries {
- flags := ""
- if entry.Disabled {
- flags += "X"
- } else {
- flags += " "
- }
- tbl.AddRow(flags, entry.Name, entry.Address, entry.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- list := params["list"]
- addr := params["address"]
- if list == "" || addr == "" {
- return ColorRed + "Usage: /ip firewall address-list add list=<name> address=<cidr> [comment=...]\n" + ColorReset
- }
- entry := firewall.AddressListEntry{
- Name: list,
- Address: addr,
- Comment: params["comment"],
- Disabled: false,
- }
- if err := e.fwMgr.SaveAddressListEntry(entry); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Added %s to address list %s.\n", addr, list) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip firewall address-list remove <id>\n" + ColorReset
- }
- if err := e.fwMgr.DeleteAddressListEntry(pos[0]); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Address list entry %s removed.\n", pos[0]) + ColorReset
- default:
- return ColorRed + "Usage: /ip firewall address-list [print|add|remove]\n" + ColorReset
- }
- }
- // --- IP Service ---
- func (e *Executor) handleIPService(parts []string, user *auth.PublicUser) string {
- if e.servicesMgr == nil {
- return ColorRed + "Services manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- svcs := e.servicesMgr.ListServices()
- tbl := NewTable("NAME", "PORT", "DISABLED", "CERTIFICATE", "COMMENT")
- for _, s := range svcs {
- disStr := ColorGreen + "no" + ColorReset
- if s.Disabled {
- disStr = ColorRed + "yes" + ColorReset
- }
- certStr := "-"
- if s.Certificate != "" {
- certStr = ColorCyan + s.Certificate + ColorReset
- }
- tbl.AddRow(s.Name, fmt.Sprintf("%d", s.Port), disStr, certStr, s.Comment)
- }
- return tbl.String()
- }
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to manage services\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip service enable <name>\n" + ColorReset
- }
- name := pos[0]
- if err := e.servicesMgr.SetServiceState(name, false); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Service %s enabled.\n", name) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip service disable <name>\n" + ColorReset
- }
- name := pos[0]
- if err := e.servicesMgr.SetServiceState(name, true); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Service %s disabled.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /ip service set <name> [port=<port>] [disabled=yes|no] [certificate=<cert_name>]\n" + ColorReset
- }
- name := pos[0]
- svc, ok := e.servicesMgr.GetService(name)
- if !ok {
- return ColorRed + fmt.Sprintf("Service %q not found\n", name) + ColorReset
- }
- port := svc.Port
- disabled := svc.Disabled
- certName := svc.Certificate
- if pStr, ok := params["port"]; ok {
- if pVal, err := strconv.Atoi(pStr); err == nil && pVal > 0 && pVal < 65536 {
- port = pVal
- }
- }
- if dStr, ok := params["disabled"]; ok {
- disabled = (strings.ToLower(dStr) == "yes" || strings.ToLower(dStr) == "true")
- }
- if cStr, ok := params["certificate"]; ok {
- certName = cStr
- }
- if err := e.servicesMgr.SetService(name, port, disabled, certName); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Service %s updated (port=%d, disabled=%t, certificate=%q).\n", name, port, disabled, certName) + ColorReset
- default:
- return ColorRed + "Usage: /ip service [print|enable|disable|set]\n" + ColorReset
- }
- }
- // ==================== DYNAMIC ROUTING ====================
- func (e *Executor) handleRouting(parts []string, user *auth.PublicUser) string {
- if e.dynRoutingMgr == nil {
- return ColorRed + "Dynamic routing subsystem not available.\n" + ColorReset
- }
- if len(parts) == 0 {
- return ColorYellow + "Routing subsystem. Available: bgp, ospf, rip, bfd, babel, radv, filter\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "bgp":
- return e.handleRoutingBGP(parts[1:], user)
- case "ospf":
- return e.handleRoutingOSPF(parts[1:], user)
- case "rip":
- return e.handleRoutingRIP(parts[1:], user)
- case "bfd":
- return e.handleRoutingBFD(parts[1:], user)
- case "babel":
- return e.handleRoutingBabel(parts[1:], user)
- case "radv":
- return e.handleRoutingRAdv(parts[1:], user)
- case "filter":
- return e.handleRoutingFilter(parts[1:], user)
- default:
- return ColorRed + "Unknown routing protocol: " + sub + "\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingBGP(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleRoutingBGPInstance([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance":
- return e.handleRoutingBGPInstance(parts[1:], user)
- case "peer", "connection", "peers":
- return e.handleRoutingBGPPeer(parts[1:], user)
- case "network", "networks":
- return e.handleRoutingBGPNetwork(parts[1:], user)
- case "routes", "route":
- routes := e.dynRoutingMgr.GetBGPRoutes()
- tbl := NewTable("PREFIX", "NEXT-HOP", "PATH", "METRIC", "WEIGHT")
- for _, r := range routes {
- tbl.AddRow(r.Prefix, r.NextHop, r.Path, fmt.Sprintf("%d", r.Metric), fmt.Sprintf("%d", r.Weight))
- }
- return tbl.String()
- default:
- return ColorRed + "Usage: /routing bgp [instance|peer|network|routes]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingBGPInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- inst := e.dynRoutingMgr.GetBGPInstance()
- out := " /routing bgp instance\n"
- out += fmt.Sprintf(" enabled: %t\n", inst.Enabled)
- out += fmt.Sprintf(" as: %d\n", inst.AS)
- out += fmt.Sprintf(" router-id: %s\n", inst.RouterID)
- out += fmt.Sprintf(" hold-time: %d\n", inst.HoldTime)
- out += fmt.Sprintf(" keepalive-time: %d\n", inst.KeepaliveTime)
- out += fmt.Sprintf(" redistribute-connected: %t\n", inst.RedistributeConnected)
- out += fmt.Sprintf(" redistribute-static: %t\n", inst.RedistributeStatic)
- out += fmt.Sprintf(" redistribute-ospf: %t\n", inst.RedistributeOSPF)
- out += fmt.Sprintf(" running: %t\n", inst.Running)
- return out
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BGP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "set":
- _, params := parseArgs(parts[1:])
- inst := e.dynRoutingMgr.GetBGPInstance()
- if v, ok := params["as"]; ok {
- var as uint32
- fmt.Sscanf(v, "%d", &as)
- if as > 0 {
- inst.AS = as
- }
- }
- if v, ok := params["router-id"]; ok {
- inst.RouterID = strings.TrimSpace(v)
- }
- if v, ok := params["enabled"]; ok {
- inst.Enabled = parseCLIEnableDisable(v, inst.Enabled)
- }
- if v, ok := params["hold-time"]; ok {
- fmt.Sscanf(v, "%d", &inst.HoldTime)
- }
- if v, ok := params["keepalive-time"]; ok {
- fmt.Sscanf(v, "%d", &inst.KeepaliveTime)
- }
- if v, ok := params["redistribute-connected"]; ok {
- inst.RedistributeConnected = parseCLIEnableDisable(v, inst.RedistributeConnected)
- }
- if v, ok := params["redistribute-static"]; ok {
- inst.RedistributeStatic = parseCLIEnableDisable(v, inst.RedistributeStatic)
- }
- if v, ok := params["redistribute-ospf"]; ok {
- inst.RedistributeOSPF = parseCLIEnableDisable(v, inst.RedistributeOSPF)
- }
- if err := e.dynRoutingMgr.UpdateBGPInstance(inst); err != nil {
- return ColorRed + "Error updating BGP instance: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + "BGP instance updated.\n" + ColorReset
- default:
- return ColorRed + "Usage: /routing bgp instance [print|set]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingBGPPeer(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- peers := e.dynRoutingMgr.ListBGPPeers()
- tbl := NewTable("#", "FLAGS", "NAME", "REMOTE-AS", "REMOTE-ADDRESS", "LOCAL-AS", "BFD", "STATUS", "UPTIME")
- for idx, p := range peers {
- flags := ""
- if p.Disabled {
- flags += "X"
- } else {
- flags += "E"
- }
- st := p.State
- if st == "" {
- st = "Active"
- }
- bfdStr := "no"
- if p.UseBFD {
- bfdStr = "yes"
- }
- localASStr := "-"
- if p.LocalAS > 0 {
- localASStr = fmt.Sprintf("%d", p.LocalAS)
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), flags, p.Name, fmt.Sprintf("%d", p.RemoteAS), p.RemoteAddress, localASStr, bfdStr, st, p.Uptime)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BGP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- remoteAddr := params["remote-address"]
- remoteASStr := params["remote-as"]
- if remoteAddr == "" || remoteASStr == "" {
- return ColorRed + "Usage: /routing bgp peer add name=<name> remote-address=<ip> remote-as=<asn> [use-bfd=yes/no] [local-as=<asn>] [local-address=<ip>] [multihop=yes/no]\n" + ColorReset
- }
- var remoteAS uint32
- fmt.Sscanf(remoteASStr, "%d", &remoteAS)
- if remoteAS == 0 {
- return ColorRed + "Invalid remote-as\n" + ColorReset
- }
- if name == "" {
- name = fmt.Sprintf("peer-%s", remoteAddr)
- }
- peer := dynamicrouting.BGPPeer{
- Name: name,
- RemoteAddress: remoteAddr,
- RemoteAS: remoteAS,
- Enabled: true,
- Disabled: false,
- Comment: params["comment"],
- InFilter: params["in-filter"],
- OutFilter: params["out-filter"],
- Password: params["password"],
- }
- if v, ok := params["local-as"]; ok {
- fmt.Sscanf(v, "%d", &peer.LocalAS)
- }
- if v, ok := params["local-address"]; ok {
- peer.LocalAddress = v
- }
- if v, ok := params["use-bfd"]; ok {
- peer.UseBFD = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["multihop"]; ok {
- peer.Multihop = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["disabled"]; ok {
- peer.Disabled = parseCLIEnableDisable(v, false)
- }
- if err := e.dynRoutingMgr.SaveBGPPeer(&peer); err != nil {
- return ColorRed + "Error adding BGP peer: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP peer %s (%s, AS%d) added.\n", peer.Name, peer.RemoteAddress, peer.RemoteAS) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bgp peer remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- peers := e.dynRoutingMgr.ListBGPPeers()
- var peerID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(peers) {
- peerID = peers[idx].ID
- } else {
- for _, p := range peers {
- if strings.EqualFold(p.Name, target) || p.ID == target || p.RemoteAddress == target {
- peerID = p.ID
- break
- }
- }
- }
- if peerID == "" {
- return ColorRed + "BGP peer not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBGPPeer(peerID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP peer %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bgp peer enable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- peers := e.dynRoutingMgr.ListBGPPeers()
- var peerID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(peers) {
- peerID = peers[idx].ID
- } else {
- for _, p := range peers {
- if strings.EqualFold(p.Name, target) || p.ID == target || p.RemoteAddress == target {
- peerID = p.ID
- break
- }
- }
- }
- if peerID == "" {
- return ColorRed + "BGP peer not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.SetBGPPeerState(peerID, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP peer %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bgp peer disable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- peers := e.dynRoutingMgr.ListBGPPeers()
- var peerID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(peers) {
- peerID = peers[idx].ID
- } else {
- for _, p := range peers {
- if strings.EqualFold(p.Name, target) || p.ID == target || p.RemoteAddress == target {
- peerID = p.ID
- break
- }
- }
- }
- if peerID == "" {
- return ColorRed + "BGP peer not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.SetBGPPeerState(peerID, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP peer %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing bgp peer [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingBGPNetwork(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- nets := e.dynRoutingMgr.ListBGPNetworks()
- tbl := NewTable("#", "NETWORK", "COMMENT")
- for idx, n := range nets {
- tbl.AddRow(fmt.Sprintf("%d", idx), n.Network, n.Comment)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BGP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- network := params["network"]
- if network == "" {
- return ColorRed + "Usage: /routing bgp network add network=<cidr> [comment=...]\n" + ColorReset
- }
- n := dynamicrouting.BGPNetwork{
- Network: network,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveBGPNetwork(n); err != nil {
- return ColorRed + "Error adding network: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP network %s added.\n", network) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bgp network remove <#|cidr|id>\n" + ColorReset
- }
- target := pos[0]
- nets := e.dynRoutingMgr.ListBGPNetworks()
- var netID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nets) {
- netID = nets[idx].ID
- } else {
- for _, n := range nets {
- if n.Network == target || n.ID == target {
- netID = n.ID
- break
- }
- }
- }
- if netID == "" {
- return ColorRed + "BGP network not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBGPNetwork(netID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BGP network %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing bgp network [print|add|remove]\n" + ColorReset
- }
- }
- // ==================== RIP PROTOCOL ====================
- func (e *Executor) handleRoutingRIP(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleRIPInstance([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance", "instances":
- return e.handleRIPInstance(parts[1:], user)
- case "interface", "interfaces":
- return e.handleRIPInterface(parts[1:], user)
- case "neighbor", "neighbors":
- return e.handleRIPNeighbor(parts[1:], user)
- case "routes", "route":
- routes := e.dynRoutingMgr.GetRIPRoutes()
- tbl := NewTable("PREFIX", "NEXT-HOP", "INTERFACE", "METRIC")
- for _, r := range routes {
- tbl.AddRow(r.Prefix, r.NextHop, r.Interface, fmt.Sprintf("%d", r.Metric))
- }
- return tbl.String()
- default:
- return ColorRed + "Usage: /routing rip [instance|interface|neighbor|routes]\n" + ColorReset
- }
- }
- func (e *Executor) handleRIPInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- instances := e.dynRoutingMgr.ListRIPInstances()
- tbl := NewTable("#", "NAME", "VERSION", "REDISTRIBUTE", "UPDATE-TIME", "STATUS")
- for idx, inst := range instances {
- st := "enabled"
- if inst.Disabled {
- st = "disabled"
- }
- var redist []string
- if inst.RedistributeConnected {
- redist = append(redist, "connected")
- }
- if inst.RedistributeStatic {
- redist = append(redist, "static")
- }
- if inst.RedistributeOSPF {
- redist = append(redist, "ospf")
- }
- if inst.RedistributeBGP {
- redist = append(redist, "bgp")
- }
- if inst.RedistributeDefault {
- redist = append(redist, "default")
- }
- redistStr := "-"
- if len(redist) > 0 {
- redistStr = strings.Join(redist, ",")
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), inst.Name, fmt.Sprintf("%d", inst.Version), redistStr, fmt.Sprintf("%ds", inst.UpdateTime), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify RIP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- name = "default"
- }
- ver := 2
- if v, ok := params["version"]; ok {
- fmt.Sscanf(v, "%d", &ver)
- }
- inst := dynamicrouting.RIPInstance{
- Name: name,
- Version: ver,
- Enabled: true,
- Disabled: false,
- RedistributeConnected: parseCLIEnableDisable(params["redistribute-connected"], false),
- RedistributeStatic: parseCLIEnableDisable(params["redistribute-static"], false),
- RedistributeOSPF: parseCLIEnableDisable(params["redistribute-ospf"], false),
- RedistributeBGP: parseCLIEnableDisable(params["redistribute-bgp"], false),
- RedistributeDefault: parseCLIEnableDisable(params["redistribute-default"], false),
- UpdateTime: 30,
- TimeoutTime: 180,
- }
- if v, ok := params["update-time"]; ok {
- fmt.Sscanf(v, "%d", &inst.UpdateTime)
- }
- if v, ok := params["timeout-time"]; ok {
- fmt.Sscanf(v, "%d", &inst.TimeoutTime)
- }
- if v, ok := params["default-metric"]; ok {
- fmt.Sscanf(v, "%d", &inst.DefaultMetric)
- }
- if _, err := e.dynRoutingMgr.SaveRIPInstance(inst); err != nil {
- return ColorRed + "Error adding RIP instance: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP instance %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip instance remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- instances := e.dynRoutingMgr.ListRIPInstances()
- var instID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- instID = instances[idx].ID
- } else {
- for _, inst := range instances {
- if strings.EqualFold(inst.Name, target) || inst.ID == target {
- instID = inst.ID
- break
- }
- }
- }
- if instID == "" {
- return ColorRed + "RIP instance not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRIPInstance(instID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP instance %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip instance enable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRIPInstanceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP instance %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip instance disable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRIPInstanceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP instance %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing rip instance [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleRIPInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.dynRoutingMgr.ListRIPInterfaces()
- tbl := NewTable("#", "INTERFACE", "INSTANCE", "METRIC", "MODE", "PASSIVE", "BFD", "STATUS")
- for idx, iface := range ifaces {
- st := "enabled"
- if iface.Disabled {
- st = "disabled"
- }
- bfdStr := "no"
- if iface.UseBFD {
- bfdStr = "yes"
- }
- passStr := "no"
- if iface.Passive {
- passStr = "yes"
- }
- mode := iface.Mode
- if mode == "" {
- mode = "multicast"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), iface.Interface, iface.Instance, fmt.Sprintf("%d", iface.Metric), mode, passStr, bfdStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify RIP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- ifaceName := params["interface"]
- if ifaceName == "" {
- ifaceName = "*"
- }
- instName := params["instance"]
- if instName == "" {
- instName = "default"
- }
- metric := 1
- if v, ok := params["metric"]; ok {
- fmt.Sscanf(v, "%d", &metric)
- }
- mode := params["mode"]
- if mode == "" {
- mode = "multicast"
- }
- iface := dynamicrouting.RIPInterface{
- Interface: ifaceName,
- Instance: instName,
- Metric: metric,
- Mode: mode,
- Passive: parseCLIEnableDisable(params["passive"], false),
- UseBFD: parseCLIEnableDisable(params["use-bfd"], false),
- Auth: params["auth"],
- AuthKey: params["auth-key"],
- }
- if _, err := e.dynRoutingMgr.SaveRIPInterface(iface); err != nil {
- return ColorRed + "Error adding RIP interface: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP interface %s added.\n", ifaceName) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip interface remove <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- ifaces := e.dynRoutingMgr.ListRIPInterfaces()
- var ifaceID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- ifaceID = ifaces[idx].ID
- } else {
- for _, iface := range ifaces {
- if iface.Interface == target || iface.ID == target {
- ifaceID = iface.ID
- break
- }
- }
- }
- if ifaceID == "" {
- return ColorRed + "RIP interface not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRIPInterface(ifaceID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP interface %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip interface enable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRIPInterfaceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP interface %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip interface disable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRIPInterfaceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP interface %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing rip interface [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleRIPNeighbor(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- liveNbrs := e.dynRoutingMgr.GetRIPNeighbors()
- tbl := NewTable("IP-ADDRESS", "INTERFACE", "LAST-SEEN")
- for _, n := range liveNbrs {
- tbl.AddRow(n.Address, n.Interface, n.LastSeen)
- }
- cfgNbrs := e.dynRoutingMgr.ListRIPNeighbors()
- if len(cfgNbrs) > 0 {
- tblStatic := NewTable("#", "CONFIGURED-NEIGHBOR", "INSTANCE")
- for idx, n := range cfgNbrs {
- tblStatic.AddRow(fmt.Sprintf("%d", idx), n.Address, n.Instance)
- }
- return tbl.String() + "\n" + tblStatic.String()
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify RIP\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- addr := params["address"]
- if addr == "" {
- return ColorRed + "Usage: /routing rip neighbor add address=<ip> [instance=<name>]\n" + ColorReset
- }
- nbr := dynamicrouting.RIPNeighbor{
- Address: addr,
- Instance: params["instance"],
- }
- if _, err := e.dynRoutingMgr.SaveRIPNeighbor(nbr); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP neighbor %s added.\n", addr) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing rip neighbor remove <#|ip|id>\n" + ColorReset
- }
- target := pos[0]
- nbrs := e.dynRoutingMgr.ListRIPNeighbors()
- var nbrID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nbrs) {
- nbrID = nbrs[idx].ID
- } else {
- for _, n := range nbrs {
- if n.Address == target || n.ID == target {
- nbrID = n.ID
- break
- }
- }
- }
- if nbrID == "" {
- return ColorRed + "RIP neighbor not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRIPNeighbor(nbrID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RIP neighbor %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing rip neighbor [print|add|remove]\n" + ColorReset
- }
- }
- // ==================== BFD PROTOCOL ====================
- func (e *Executor) handleRoutingBFD(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleBFDInstance([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance":
- return e.handleBFDInstance(parts[1:], user)
- case "interface", "interfaces":
- return e.handleBFDInterface(parts[1:], user)
- case "peer", "peers", "neighbor", "neighbors":
- return e.handleBFDNeighbor(parts[1:], user)
- case "session", "sessions":
- sessions := e.dynRoutingMgr.GetBFDSessions()
- tbl := NewTable("IP-ADDRESS", "INTERFACE", "STATE", "SINCE", "INTERVAL", "TIMEOUT")
- for _, s := range sessions {
- st := s.State
- if strings.EqualFold(st, "Up") {
- st = ColorGreen + st + ColorReset
- } else if strings.EqualFold(st, "Down") {
- st = ColorRed + st + ColorReset
- }
- tbl.AddRow(s.IP, s.Interface, st, s.Since, s.Interval, s.Timeout)
- }
- return tbl.String()
- default:
- return ColorRed + "Usage: /routing bfd [instance|interface|peer|session]\n" + ColorReset
- }
- }
- func (e *Executor) handleBFDInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- inst := e.dynRoutingMgr.GetBFDInstance()
- out := " /routing bfd instance\n"
- out += fmt.Sprintf(" enabled: %t\n", inst.Enabled)
- out += fmt.Sprintf(" disabled: %t\n", inst.Disabled)
- out += fmt.Sprintf(" running: %t\n", inst.Running)
- return out
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BFD\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "set":
- _, params := parseArgs(parts[1:])
- inst := e.dynRoutingMgr.GetBFDInstance()
- if v, ok := params["enabled"]; ok {
- inst.Enabled = parseCLIEnableDisable(v, inst.Enabled)
- inst.Disabled = !inst.Enabled
- }
- if v, ok := params["disabled"]; ok {
- inst.Disabled = parseCLIEnableDisable(v, inst.Disabled)
- inst.Enabled = !inst.Disabled
- }
- if err := e.dynRoutingMgr.SaveBFDInstance(inst); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + "BFD instance updated.\n" + ColorReset
- default:
- return ColorRed + "Usage: /routing bfd instance [print|set]\n" + ColorReset
- }
- }
- func (e *Executor) handleBFDInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.dynRoutingMgr.ListBFDInterfaces()
- tbl := NewTable("#", "INTERFACE", "MIN-RX", "MIN-TX", "MULTIPLIER", "PASSIVE", "STATUS")
- for idx, iface := range ifaces {
- st := "enabled"
- if iface.Disabled {
- st = "disabled"
- }
- passStr := "no"
- if iface.Passive {
- passStr = "yes"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), iface.Interface, fmt.Sprintf("%dms", iface.MinRxInterval), fmt.Sprintf("%dms", iface.MinTxInterval), fmt.Sprintf("%d", iface.Multiplier), passStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BFD\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- ifaceName := params["interface"]
- if ifaceName == "" {
- ifaceName = "*"
- }
- minRx := 100
- if v, ok := params["min-rx"]; ok {
- fmt.Sscanf(v, "%d", &minRx)
- }
- minTx := 100
- if v, ok := params["min-tx"]; ok {
- fmt.Sscanf(v, "%d", &minTx)
- }
- mult := 3
- if v, ok := params["multiplier"]; ok {
- fmt.Sscanf(v, "%d", &mult)
- }
- iface := dynamicrouting.BFDInterface{
- Interface: ifaceName,
- MinRxInterval: minRx,
- MinTxInterval: minTx,
- Multiplier: mult,
- Passive: parseCLIEnableDisable(params["passive"], false),
- Auth: params["auth"],
- AuthKey: params["auth-key"],
- }
- if _, err := e.dynRoutingMgr.SaveBFDInterface(iface); err != nil {
- return ColorRed + "Error adding BFD interface: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD interface %s added.\n", ifaceName) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bfd interface remove <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- ifaces := e.dynRoutingMgr.ListBFDInterfaces()
- var ifaceID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- ifaceID = ifaces[idx].ID
- } else {
- for _, iface := range ifaces {
- if iface.Interface == target || iface.ID == target {
- ifaceID = iface.ID
- break
- }
- }
- }
- if ifaceID == "" {
- return ColorRed + "BFD interface not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBFDInterface(ifaceID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD interface %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bfd interface enable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBFDInterfaceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD interface %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bfd interface disable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBFDInterfaceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD interface %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing bfd interface [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleBFDNeighbor(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- nbrs := e.dynRoutingMgr.ListBFDNeighbors()
- tbl := NewTable("#", "ADDRESS", "INTERFACE", "MULTIHOP")
- for idx, n := range nbrs {
- mhop := "no"
- if n.Multihop {
- mhop = "yes"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), n.Address, n.Interface, mhop)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify BFD\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- addr := params["address"]
- if addr == "" {
- return ColorRed + "Usage: /routing bfd peer add address=<ip> [interface=<iface>] [multihop=yes/no]\n" + ColorReset
- }
- nbr := dynamicrouting.BFDNeighbor{
- Address: addr,
- Interface: params["interface"],
- Multihop: parseCLIEnableDisable(params["multihop"], false),
- }
- if _, err := e.dynRoutingMgr.SaveBFDNeighbor(nbr); err != nil {
- return ColorRed + "Error adding BFD peer: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD peer %s added.\n", addr) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing bfd peer remove <#|ip|id>\n" + ColorReset
- }
- target := pos[0]
- nbrs := e.dynRoutingMgr.ListBFDNeighbors()
- var nbrID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nbrs) {
- nbrID = nbrs[idx].ID
- } else {
- for _, n := range nbrs {
- if n.Address == target || n.ID == target {
- nbrID = n.ID
- break
- }
- }
- }
- if nbrID == "" {
- return ColorRed + "BFD peer not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBFDNeighbor(nbrID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("BFD peer %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing bfd peer [print|add|remove]\n" + ColorReset
- }
- }
- // ==================== BABEL PROTOCOL ====================
- func (e *Executor) handleRoutingBabel(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleBabelInstance([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance", "instances":
- return e.handleBabelInstance(parts[1:], user)
- case "interface", "interfaces":
- return e.handleBabelInterface(parts[1:], user)
- case "neighbor", "neighbors":
- nbrs := e.dynRoutingMgr.GetBabelNeighbors()
- tbl := NewTable("IP-ADDRESS", "INTERFACE", "METRIC", "COST", "UPTIME")
- for _, n := range nbrs {
- tbl.AddRow(n.IP, n.Interface, fmt.Sprintf("%d", n.Metric), fmt.Sprintf("%d", n.Cost), n.Uptime)
- }
- return tbl.String()
- case "routes", "route":
- routes := e.dynRoutingMgr.GetBabelRoutes()
- tbl := NewTable("PREFIX", "NEXT-HOP", "INTERFACE", "METRIC")
- for _, r := range routes {
- tbl.AddRow(r.Prefix, r.NextHop, r.Interface, fmt.Sprintf("%d", r.Metric))
- }
- return tbl.String()
- default:
- return ColorRed + "Usage: /routing babel [instance|interface|neighbor|routes]\n" + ColorReset
- }
- }
- func (e *Executor) handleBabelInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- instances := e.dynRoutingMgr.ListBabelInstances()
- tbl := NewTable("#", "NAME", "ROUTER-ID", "REDISTRIBUTE", "STATUS")
- for idx, inst := range instances {
- st := "enabled"
- if inst.Disabled {
- st = "disabled"
- }
- var redist []string
- if inst.RedistributeConnected {
- redist = append(redist, "connected")
- }
- if inst.RedistributeStatic {
- redist = append(redist, "static")
- }
- if inst.RedistributeOSPF {
- redist = append(redist, "ospf")
- }
- if inst.RedistributeBGP {
- redist = append(redist, "bgp")
- }
- if inst.RedistributeDefault {
- redist = append(redist, "default")
- }
- redistStr := "-"
- if len(redist) > 0 {
- redistStr = strings.Join(redist, ",")
- }
- rID := inst.RouterID
- if rID == "" {
- rID = "auto"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), inst.Name, rID, redistStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify Babel\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- name = "default"
- }
- inst := dynamicrouting.BabelInstance{
- Name: name,
- RouterID: params["router-id"],
- Enabled: true,
- Disabled: false,
- RedistributeConnected: parseCLIEnableDisable(params["redistribute-connected"], false),
- RedistributeStatic: parseCLIEnableDisable(params["redistribute-static"], false),
- RedistributeOSPF: parseCLIEnableDisable(params["redistribute-ospf"], false),
- RedistributeBGP: parseCLIEnableDisable(params["redistribute-bgp"], false),
- RedistributeDefault: parseCLIEnableDisable(params["redistribute-default"], false),
- }
- if _, err := e.dynRoutingMgr.SaveBabelInstance(inst); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel instance %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel instance remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- instances := e.dynRoutingMgr.ListBabelInstances()
- var instID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- instID = instances[idx].ID
- } else {
- for _, inst := range instances {
- if strings.EqualFold(inst.Name, target) || inst.ID == target {
- instID = inst.ID
- break
- }
- }
- }
- if instID == "" {
- return ColorRed + "Babel instance not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBabelInstance(instID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel instance %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel instance enable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBabelInstanceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel instance %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel instance disable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBabelInstanceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel instance %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing babel instance [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleBabelInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.dynRoutingMgr.ListBabelInterfaces()
- tbl := NewTable("#", "INTERFACE", "INSTANCE", "TYPE", "RXCOST", "HELLO-INT", "UPDATE-INT", "STATUS")
- for idx, iface := range ifaces {
- st := "enabled"
- if iface.Disabled {
- st = "disabled"
- }
- tStr := iface.Type
- if tStr == "" {
- tStr = "wired"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), iface.Interface, iface.Instance, tStr, fmt.Sprintf("%d", iface.RxCost), fmt.Sprintf("%ds", iface.HelloInterval), fmt.Sprintf("%ds", iface.UpdateInterval), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify Babel\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- ifaceName := params["interface"]
- if ifaceName == "" {
- ifaceName = "*"
- }
- instName := params["instance"]
- if instName == "" {
- instName = "default"
- }
- tStr := params["type"]
- if tStr == "" {
- tStr = "wired"
- }
- rxcost := 10
- if v, ok := params["rxcost"]; ok {
- fmt.Sscanf(v, "%d", &rxcost)
- }
- hello := 4
- if v, ok := params["hello-interval"]; ok {
- fmt.Sscanf(v, "%d", &hello)
- }
- update := 16
- if v, ok := params["update-interval"]; ok {
- fmt.Sscanf(v, "%d", &update)
- }
- iface := dynamicrouting.BabelInterface{
- Interface: ifaceName,
- Instance: instName,
- Type: tStr,
- RxCost: rxcost,
- HelloInterval: hello,
- UpdateInterval: update,
- Auth: params["auth"],
- AuthKey: params["auth-key"],
- }
- if _, err := e.dynRoutingMgr.SaveBabelInterface(iface); err != nil {
- return ColorRed + "Error adding Babel interface: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel interface %s added.\n", ifaceName) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel interface remove <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- ifaces := e.dynRoutingMgr.ListBabelInterfaces()
- var ifaceID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- ifaceID = ifaces[idx].ID
- } else {
- for _, iface := range ifaces {
- if iface.Interface == target || iface.ID == target {
- ifaceID = iface.ID
- break
- }
- }
- }
- if ifaceID == "" {
- return ColorRed + "Babel interface not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteBabelInterface(ifaceID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel interface %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel interface enable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBabelInterfaceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel interface %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing babel interface disable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetBabelInterfaceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Babel interface %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing babel interface [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- // ==================== RADV PROTOCOL ====================
- func (e *Executor) handleRoutingRAdv(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleRAdvInterface([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance", "instances":
- return e.handleRAdvInstance(parts[1:], user)
- case "interface", "interfaces":
- return e.handleRAdvInterface(parts[1:], user)
- default:
- return ColorRed + "Usage: /routing radv [instance|interface]\n" + ColorReset
- }
- }
- func (e *Executor) handleRAdvInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- instances := e.dynRoutingMgr.ListRAdvInstances()
- tbl := NewTable("#", "NAME", "STATUS")
- for idx, inst := range instances {
- st := "enabled"
- if inst.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), inst.Name, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify RAdv\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- name = "default"
- }
- inst := dynamicrouting.RAdvInstance{
- Name: name,
- Enabled: true,
- Disabled: false,
- }
- if _, err := e.dynRoutingMgr.SaveRAdvInstance(inst); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv instance %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv instance remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- instances := e.dynRoutingMgr.ListRAdvInstances()
- var instID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- instID = instances[idx].ID
- } else {
- for _, inst := range instances {
- if strings.EqualFold(inst.Name, target) || inst.ID == target {
- instID = inst.ID
- break
- }
- }
- }
- if instID == "" {
- return ColorRed + "RAdv instance not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRAdvInstance(instID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv instance %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv instance enable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRAdvInstanceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv instance %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv instance disable <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRAdvInstanceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv instance %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing radv instance [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleRAdvInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.dynRoutingMgr.ListRAdvInterfaces()
- tbl := NewTable("#", "INTERFACE", "INSTANCE", "MIN-MAX-RA", "PREFIXES", "STATUS")
- for idx, iface := range ifaces {
- st := "enabled"
- if iface.Disabled {
- st = "disabled"
- }
- pfxStr := strings.Join(iface.Prefixes, ",")
- if pfxStr == "" {
- pfxStr = "-"
- }
- raInterval := fmt.Sprintf("%d-%ds", iface.MinRAInterval, iface.MaxRAInterval)
- tbl.AddRow(fmt.Sprintf("%d", idx), iface.Interface, iface.Instance, raInterval, pfxStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify RAdv\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- ifaceName := params["interface"]
- if ifaceName == "" {
- ifaceName = "*"
- }
- instName := params["instance"]
- if instName == "" {
- instName = "default"
- }
- minRA := 200
- if v, ok := params["min-ra-interval"]; ok {
- fmt.Sscanf(v, "%d", &minRA)
- }
- maxRA := 600
- if v, ok := params["max-ra-interval"]; ok {
- fmt.Sscanf(v, "%d", &maxRA)
- }
- lifetime := 1800
- if v, ok := params["default-lifetime"]; ok {
- fmt.Sscanf(v, "%d", &lifetime)
- }
- var pfxs []string
- if v, ok := params["prefixes"]; ok && v != "" {
- pfxs = strings.Split(v, ",")
- }
- var rdnss []string
- if v, ok := params["rdnss"]; ok && v != "" {
- rdnss = strings.Split(v, ",")
- }
- iface := dynamicrouting.RAdvInterface{
- Interface: ifaceName,
- Instance: instName,
- MinRAInterval: minRA,
- MaxRAInterval: maxRA,
- DefaultLifetime: lifetime,
- Prefixes: pfxs,
- RDNSS: rdnss,
- }
- if _, err := e.dynRoutingMgr.SaveRAdvInterface(iface); err != nil {
- return ColorRed + "Error adding RAdv interface: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv interface %s added.\n", ifaceName) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv interface remove <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- ifaces := e.dynRoutingMgr.ListRAdvInterfaces()
- var ifaceID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- ifaceID = ifaces[idx].ID
- } else {
- for _, iface := range ifaces {
- if iface.Interface == target || iface.ID == target {
- ifaceID = iface.ID
- break
- }
- }
- }
- if ifaceID == "" {
- return ColorRed + "RAdv interface not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRAdvInterface(ifaceID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv interface %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv interface enable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRAdvInterfaceState(target, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv interface %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing radv interface disable <#|iface|id>\n" + ColorReset
- }
- target := pos[0]
- if err := e.dynRoutingMgr.SetRAdvInterfaceState(target, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("RAdv interface %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing radv interface [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func parseCLIEnableDisable(val string, defaultVal bool) bool {
- v := strings.ToLower(strings.TrimSpace(val))
- if v == "yes" || v == "true" || v == "1" || v == "enable" || v == "enabled" {
- return true
- }
- if v == "no" || v == "false" || v == "0" || v == "disable" || v == "disabled" {
- return false
- }
- return defaultVal
- }
- func (e *Executor) handleRoutingOSPF(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleOSPFInstance([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "instance", "instances":
- return e.handleOSPFInstance(parts[1:], user)
- case "area", "areas":
- if len(parts) > 1 && (strings.EqualFold(parts[1], "range") || strings.EqualFold(parts[1], "ranges")) {
- return e.handleOSPFAreaRange(parts[2:], user)
- }
- return e.handleOSPFArea(parts[1:], user)
- case "area-range", "area-ranges", "range", "ranges":
- return e.handleOSPFAreaRange(parts[1:], user)
- case "interface", "interfaces", "template", "templates", "interface-template", "interface-templates":
- return e.handleOSPFInterface(parts[1:], user)
- case "static-neighbor", "static-neighbors":
- return e.handleOSPFStaticNeighbor(parts[1:], user)
- case "neighbor", "neighbors":
- return e.handleOSPFNeighborLive(parts[1:], user)
- case "lsa", "lsadb":
- return e.handleOSPFLSA(parts[1:], user)
- case "route", "routes":
- return e.handleOSPFRoute(parts[1:], user)
- default:
- return ColorRed + "Usage: /routing ospf [instance|area|area-range|interface|static-neighbor|neighbor|lsa|routes]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFInstance(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- instances := e.dynRoutingMgr.ListOSPFInstances()
- tbl := NewTable("#", "NAME", "ROUTER-ID", "VERSION", "REDISTRIBUTE", "DEFAULT-METRIC", "STATUS")
- for idx, inst := range instances {
- st := "enabled"
- if inst.Disabled {
- st = "disabled"
- }
- var redist []string
- if inst.RedistributeConnected {
- redist = append(redist, "connected")
- }
- if inst.RedistributeStatic {
- redist = append(redist, "static")
- }
- if inst.RedistributeBGP {
- redist = append(redist, "bgp")
- }
- if inst.RedistributeDefault || (inst.OriginateDefault != "" && inst.OriginateDefault != "never") {
- mode := inst.OriginateDefault
- if mode == "" || mode == "never" {
- mode = "if-installed"
- }
- redist = append(redist, fmt.Sprintf("default(%s)", mode))
- }
- redistStr := "-"
- if len(redist) > 0 {
- redistStr = strings.Join(redist, ",")
- }
- metricStr := "-"
- if inst.DefaultMetric > 0 {
- metricStr = fmt.Sprintf("%d", inst.DefaultMetric)
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), inst.Name, inst.RouterID, fmt.Sprintf("%d", inst.Version), redistStr, metricStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify OSPF\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- name = "Default"
- }
- routerID := params["router-id"]
- version := 2
- if vStr, ok := params["version"]; ok && vStr != "" {
- if v, err := strconv.Atoi(vStr); err == nil && (v == 2 || v == 3) {
- version = v
- }
- }
- redistConn := parseCLIEnableDisable(params["redistribute-connected"], false)
- redistStat := parseCLIEnableDisable(params["redistribute-static"], false)
- redistBGP := parseCLIEnableDisable(params["redistribute-bgp"], false)
- redistDef := parseCLIEnableDisable(params["redistribute-default"], false)
- origDef := strings.ToLower(strings.TrimSpace(params["originate-default"]))
- if origDef == "" {
- if redistDef {
- origDef = "if-installed"
- } else {
- origDef = "never"
- }
- } else if origDef == "if-installed" || origDef == "always" {
- redistDef = true
- }
- metric := 0
- if mStr, ok := params["default-metric"]; ok && mStr != "" {
- metric, _ = strconv.Atoi(mStr)
- }
- disabled := parseCLIEnableDisable(params["disabled"], false)
- vrf := params["vrf"]
- if vrf == "" {
- vrf = "main"
- }
- rt := params["routing-table"]
- if rt == "" {
- rt = "main"
- }
- inst := dynamicrouting.OSPFInstance{
- Name: name,
- Version: version,
- VRF: vrf,
- RouterID: routerID,
- RoutingTable: rt,
- Enabled: !disabled,
- Disabled: disabled,
- RedistributeConnected: redistConn,
- RedistributeStatic: redistStat,
- RedistributeBGP: redistBGP,
- RedistributeDefault: redistDef,
- OriginateDefault: origDef,
- DefaultMetric: metric,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveOSPFInstance(&inst); err != nil {
- return ColorRed + "Error saving OSPF instance: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF instance %s saved.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- if len(pos) == 0 && params["numbers"] == "" {
- return ColorRed + "Usage: /routing ospf instance set <number|name> [router-id=...] [redistribute-connected=yes/no] [redistribute-static=yes/no] [redistribute-bgp=yes/no] [redistribute-default=yes/no] [originate-default=never|if-installed|always] ...\n" + ColorReset
- }
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else {
- target = params["numbers"]
- }
- instances := e.dynRoutingMgr.ListOSPFInstances()
- var targetInst *dynamicrouting.OSPFInstance
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- targetInst = &instances[idx]
- } else {
- for i := range instances {
- if strings.EqualFold(instances[i].Name, target) || instances[i].ID == target {
- targetInst = &instances[i]
- break
- }
- }
- }
- if targetInst == nil {
- return ColorRed + fmt.Sprintf("OSPF instance %q not found\n", target) + ColorReset
- }
- if v, ok := params["name"]; ok && v != "" {
- targetInst.Name = v
- }
- if v, ok := params["router-id"]; ok {
- targetInst.RouterID = v
- }
- if v, ok := params["redistribute-connected"]; ok {
- targetInst.RedistributeConnected = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["redistribute-static"]; ok {
- targetInst.RedistributeStatic = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["redistribute-bgp"]; ok {
- targetInst.RedistributeBGP = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["redistribute-default"]; ok {
- targetInst.RedistributeDefault = parseCLIEnableDisable(v, false)
- if targetInst.RedistributeDefault && (targetInst.OriginateDefault == "" || targetInst.OriginateDefault == "never") {
- targetInst.OriginateDefault = "if-installed"
- } else if !targetInst.RedistributeDefault {
- targetInst.OriginateDefault = "never"
- }
- }
- if v, ok := params["originate-default"]; ok {
- vLower := strings.ToLower(strings.TrimSpace(v))
- targetInst.OriginateDefault = vLower
- if vLower == "if-installed" || vLower == "always" {
- targetInst.RedistributeDefault = true
- } else if vLower == "never" {
- targetInst.RedistributeDefault = false
- }
- }
- if v, ok := params["default-metric"]; ok {
- targetInst.DefaultMetric, _ = strconv.Atoi(v)
- }
- if v, ok := params["disabled"]; ok {
- targetInst.Disabled = parseCLIEnableDisable(v, false)
- targetInst.Enabled = !targetInst.Disabled
- }
- if v, ok := params["comment"]; ok {
- targetInst.Comment = v
- }
- if err := e.dynRoutingMgr.SaveOSPFInstance(targetInst); err != nil {
- return ColorRed + "Error updating OSPF instance: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF instance %s updated.\n", targetInst.Name) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf instance remove <number|name>\n" + ColorReset
- }
- instances := e.dynRoutingMgr.ListOSPFInstances()
- idToDelete := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- idToDelete = instances[idx].ID
- } else {
- for _, inst := range instances {
- if strings.EqualFold(inst.Name, target) || inst.ID == target {
- idToDelete = inst.ID
- break
- }
- }
- }
- if idToDelete == "" {
- return ColorRed + fmt.Sprintf("OSPF instance %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteOSPFInstance(idToDelete); err != nil {
- return ColorRed + "Error deleting OSPF instance: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF instance %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf instance enable <number|name>\n" + ColorReset
- }
- return e.setOSPFInstanceStateByTarget(pos[0], false)
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf instance disable <number|name>\n" + ColorReset
- }
- return e.setOSPFInstanceStateByTarget(pos[0], true)
- default:
- return ColorRed + "Usage: /routing ospf instance [print|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFArea(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- areas := e.dynRoutingMgr.ListOSPFAreas()
- tbl := NewTable("#", "NAME", "AREA-ID", "INSTANCE", "TYPE", "DEFAULT-COST", "NO-SUMMARIES", "STATUS")
- for idx, a := range areas {
- st := "enabled"
- if a.Disabled {
- st = "disabled"
- }
- costStr := "-"
- if a.DefaultCost > 0 {
- costStr = fmt.Sprintf("%d", a.DefaultCost)
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), a.Name, a.AreaID, a.Instance, a.AreaType, costStr, fmt.Sprintf("%t", a.NoSummaries), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify OSPF\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- return ColorRed + "Usage: /routing ospf area add name=<name> [area-id=...] [instance=...] [type=default|stub|nssa]\n" + ColorReset
- }
- areaID := params["area-id"]
- if areaID == "" {
- areaID = "0.0.0.0"
- }
- instName := params["instance"]
- if instName == "" {
- instName = "Default"
- }
- areaType := params["type"]
- if areaType == "" {
- areaType = "default"
- }
- defaultCost := 0
- if cStr, ok := params["default-cost"]; ok && cStr != "" {
- defaultCost, _ = strconv.Atoi(cStr)
- }
- noSummaries := parseCLIEnableDisable(params["no-summaries"], false)
- disabled := parseCLIEnableDisable(params["disabled"], false)
- area := dynamicrouting.OSPFArea{
- Name: name,
- AreaID: areaID,
- Instance: instName,
- AreaType: areaType,
- DefaultCost: defaultCost,
- NoSummaries: noSummaries,
- Disabled: disabled,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveOSPFArea(area); err != nil {
- return ColorRed + "Error saving OSPF area: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF area %s saved.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf area set <number|name> [area-id=...] [type=...] ...\n" + ColorReset
- }
- areas := e.dynRoutingMgr.ListOSPFAreas()
- var targetArea *dynamicrouting.OSPFArea
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(areas) {
- targetArea = &areas[idx]
- } else {
- for i := range areas {
- if strings.EqualFold(areas[i].Name, target) || areas[i].ID == target {
- targetArea = &areas[i]
- break
- }
- }
- }
- if targetArea == nil {
- return ColorRed + fmt.Sprintf("OSPF area %q not found\n", target) + ColorReset
- }
- if v, ok := params["name"]; ok && v != "" {
- targetArea.Name = v
- }
- if v, ok := params["area-id"]; ok && v != "" {
- targetArea.AreaID = v
- }
- if v, ok := params["instance"]; ok && v != "" {
- targetArea.Instance = v
- }
- if v, ok := params["type"]; ok && v != "" {
- targetArea.AreaType = v
- }
- if v, ok := params["default-cost"]; ok {
- targetArea.DefaultCost, _ = strconv.Atoi(v)
- }
- if v, ok := params["no-summaries"]; ok {
- targetArea.NoSummaries = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["disabled"]; ok {
- targetArea.Disabled = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["comment"]; ok {
- targetArea.Comment = v
- }
- if err := e.dynRoutingMgr.SaveOSPFArea(*targetArea); err != nil {
- return ColorRed + "Error updating OSPF area: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF area %s updated.\n", targetArea.Name) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf area remove <number|name>\n" + ColorReset
- }
- areas := e.dynRoutingMgr.ListOSPFAreas()
- idToDelete := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(areas) {
- idToDelete = areas[idx].ID
- } else {
- for _, a := range areas {
- if strings.EqualFold(a.Name, target) || a.ID == target {
- idToDelete = a.ID
- break
- }
- }
- }
- if idToDelete == "" {
- return ColorRed + fmt.Sprintf("OSPF area %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteOSPFArea(idToDelete); err != nil {
- return ColorRed + "Error deleting OSPF area: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF area %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf area enable <number|name>\n" + ColorReset
- }
- return e.setOSPFAreaStateByTarget(pos[0], false)
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf area disable <number|name>\n" + ColorReset
- }
- return e.setOSPFAreaStateByTarget(pos[0], true)
- default:
- return ColorRed + "Usage: /routing ospf area [print|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFAreaRange(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ranges := e.dynRoutingMgr.ListOSPFAreaRanges()
- tbl := NewTable("#", "PREFIX", "AREA", "COST", "ADVERTISE", "STATUS")
- for idx, r := range ranges {
- st := "enabled"
- if r.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), r.Prefix, r.Area, fmt.Sprintf("%d", r.Cost), fmt.Sprintf("%t", r.Advertise), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify OSPF\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- prefix := params["prefix"]
- if prefix == "" {
- return ColorRed + "Usage: /routing ospf area range add prefix=<cidr> [area=...] [cost=...] [advertise=yes/no]\n" + ColorReset
- }
- area := params["area"]
- if area == "" {
- area = "Default"
- }
- cost := 1
- if cStr, ok := params["cost"]; ok && cStr != "" {
- cost, _ = strconv.Atoi(cStr)
- }
- adv := true
- if aStr, ok := params["advertise"]; ok && aStr != "" {
- adv = parseCLIEnableDisable(aStr, true)
- }
- disabled := parseCLIEnableDisable(params["disabled"], false)
- ar := dynamicrouting.OSPFAreaRange{
- Prefix: prefix,
- Area: area,
- Cost: cost,
- Advertise: adv,
- Disabled: disabled,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveOSPFAreaRange(ar); err != nil {
- return ColorRed + "Error saving area range: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Area range %s added.\n", prefix) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf area range set <number|prefix> [cost=...] [advertise=yes/no]\n" + ColorReset
- }
- ranges := e.dynRoutingMgr.ListOSPFAreaRanges()
- var targetRange *dynamicrouting.OSPFAreaRange
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ranges) {
- targetRange = &ranges[idx]
- } else {
- for i := range ranges {
- if strings.EqualFold(ranges[i].Prefix, target) || ranges[i].ID == target {
- targetRange = &ranges[i]
- break
- }
- }
- }
- if targetRange == nil {
- return ColorRed + fmt.Sprintf("Area range %q not found\n", target) + ColorReset
- }
- if v, ok := params["prefix"]; ok && v != "" {
- targetRange.Prefix = v
- }
- if v, ok := params["area"]; ok && v != "" {
- targetRange.Area = v
- }
- if v, ok := params["cost"]; ok {
- targetRange.Cost, _ = strconv.Atoi(v)
- }
- if v, ok := params["advertise"]; ok {
- targetRange.Advertise = parseCLIEnableDisable(v, true)
- }
- if v, ok := params["disabled"]; ok {
- targetRange.Disabled = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["comment"]; ok {
- targetRange.Comment = v
- }
- if err := e.dynRoutingMgr.SaveOSPFAreaRange(*targetRange); err != nil {
- return ColorRed + "Error updating area range: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Area range %s updated.\n", targetRange.Prefix) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf area range remove <number|prefix>\n" + ColorReset
- }
- ranges := e.dynRoutingMgr.ListOSPFAreaRanges()
- idToDelete := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ranges) {
- idToDelete = ranges[idx].ID
- } else {
- for _, r := range ranges {
- if strings.EqualFold(r.Prefix, target) || r.ID == target {
- idToDelete = r.ID
- break
- }
- }
- }
- if idToDelete == "" {
- return ColorRed + fmt.Sprintf("Area range %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteOSPFAreaRange(idToDelete); err != nil {
- return ColorRed + "Error deleting area range: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Area range %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing ospf area range [print|add|set|remove]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- ifaces := e.dynRoutingMgr.ListOSPFInterfaces()
- tbl := NewTable("#", "INTERFACE", "AREA", "NETWORKS", "TYPE", "COST", "PRIORITY", "PASSIVE", "AUTH", "STATUS")
- for idx, i := range ifaces {
- st := "enabled"
- if i.Disabled {
- st = "disabled"
- }
- ifaceName := i.Interfaces
- if ifaceName == "" {
- ifaceName = i.Interface
- }
- if ifaceName == "" {
- ifaceName = "*"
- }
- nets := i.Networks
- if nets == "" {
- nets = i.Network
- }
- if nets == "" {
- nets = "-"
- }
- authStr := i.Auth
- if authStr == "" {
- authStr = i.AuthType
- }
- if authStr == "" {
- authStr = "none"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), ifaceName, i.Area, nets, i.NetworkType, fmt.Sprintf("%d", i.Cost), fmt.Sprintf("%d", i.Priority), fmt.Sprintf("%t", i.Passive), authStr, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify OSPF\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- ifaceName := params["interface"]
- if ifaceName == "" {
- ifaceName = params["interfaces"]
- }
- if ifaceName == "" {
- ifaceName = "*"
- }
- area := params["area"]
- if area == "" {
- area = "Default"
- }
- nets := params["networks"]
- if nets == "" {
- nets = params["network"]
- }
- netType := params["network-type"]
- if netType == "" {
- netType = "broadcast"
- }
- cost := 1
- if cStr, ok := params["cost"]; ok && cStr != "" {
- cost, _ = strconv.Atoi(cStr)
- }
- pri := 128
- if pStr, ok := params["priority"]; ok && pStr != "" {
- pri, _ = strconv.Atoi(pStr)
- }
- hello := 10
- if hStr, ok := params["hello-interval"]; ok && hStr != "" {
- hello, _ = strconv.Atoi(hStr)
- }
- dead := 40
- if dStr, ok := params["dead-interval"]; ok && dStr != "" {
- dead, _ = strconv.Atoi(dStr)
- }
- passive := parseCLIEnableDisable(params["passive"], false)
- authType := params["auth"]
- if authType == "" {
- authType = params["auth-type"]
- }
- authKey := params["auth-key"]
- disabled := parseCLIEnableDisable(params["disabled"], false)
- iface := dynamicrouting.OSPFInterface{
- Interfaces: ifaceName,
- Interface: ifaceName,
- Area: area,
- Networks: nets,
- Network: nets,
- NetworkType: netType,
- Cost: cost,
- Priority: pri,
- HelloInterval: hello,
- DeadInterval: dead,
- Passive: passive,
- Auth: authType,
- AuthType: authType,
- AuthKey: authKey,
- Disabled: disabled,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveOSPFInterface(iface); err != nil {
- return ColorRed + "Error saving OSPF interface template: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF interface template %s saved.\n", ifaceName) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf interface set <number|id> [cost=...] [priority=...] ...\n" + ColorReset
- }
- ifaces := e.dynRoutingMgr.ListOSPFInterfaces()
- var targetIface *dynamicrouting.OSPFInterface
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- targetIface = &ifaces[idx]
- } else {
- for i := range ifaces {
- if ifaces[i].ID == target || ifaces[i].Interfaces == target || ifaces[i].Interface == target {
- targetIface = &ifaces[i]
- break
- }
- }
- }
- if targetIface == nil {
- return ColorRed + fmt.Sprintf("OSPF interface template %q not found\n", target) + ColorReset
- }
- if v, ok := params["interface"]; ok && v != "" {
- targetIface.Interface = v
- targetIface.Interfaces = v
- } else if v, ok := params["interfaces"]; ok && v != "" {
- targetIface.Interface = v
- targetIface.Interfaces = v
- }
- if v, ok := params["area"]; ok && v != "" {
- targetIface.Area = v
- }
- if v, ok := params["networks"]; ok {
- targetIface.Networks = v
- targetIface.Network = v
- } else if v, ok := params["network"]; ok {
- targetIface.Networks = v
- targetIface.Network = v
- }
- if v, ok := params["network-type"]; ok && v != "" {
- targetIface.NetworkType = v
- }
- if v, ok := params["cost"]; ok {
- targetIface.Cost, _ = strconv.Atoi(v)
- }
- if v, ok := params["priority"]; ok {
- targetIface.Priority, _ = strconv.Atoi(v)
- }
- if v, ok := params["hello-interval"]; ok {
- targetIface.HelloInterval, _ = strconv.Atoi(v)
- }
- if v, ok := params["dead-interval"]; ok {
- targetIface.DeadInterval, _ = strconv.Atoi(v)
- }
- if v, ok := params["passive"]; ok {
- targetIface.Passive = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["auth"]; ok {
- targetIface.Auth = v
- targetIface.AuthType = v
- } else if v, ok := params["auth-type"]; ok {
- targetIface.Auth = v
- targetIface.AuthType = v
- }
- if v, ok := params["auth-key"]; ok {
- targetIface.AuthKey = v
- }
- if v, ok := params["disabled"]; ok {
- targetIface.Disabled = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["comment"]; ok {
- targetIface.Comment = v
- }
- if err := e.dynRoutingMgr.SaveOSPFInterface(*targetIface); err != nil {
- return ColorRed + "Error updating OSPF interface template: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF interface template %s updated.\n", targetIface.ID) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf interface remove <number|id>\n" + ColorReset
- }
- ifaces := e.dynRoutingMgr.ListOSPFInterfaces()
- idToDelete := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- idToDelete = ifaces[idx].ID
- } else {
- for _, i := range ifaces {
- if i.ID == target || i.Interfaces == target || i.Interface == target {
- idToDelete = i.ID
- break
- }
- }
- }
- if idToDelete == "" {
- return ColorRed + fmt.Sprintf("OSPF interface template %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteOSPFInterface(idToDelete); err != nil {
- return ColorRed + "Error deleting OSPF interface template: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF interface template %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf interface enable <number|id>\n" + ColorReset
- }
- return e.setOSPFInterfaceStateByTarget(pos[0], false)
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf interface disable <number|id>\n" + ColorReset
- }
- return e.setOSPFInterfaceStateByTarget(pos[0], true)
- default:
- return ColorRed + "Usage: /routing ospf interface [print|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFStaticNeighbor(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- nbrs := e.dynRoutingMgr.ListOSPFStaticNeighbors()
- tbl := NewTable("#", "ADDRESS", "AREA", "POLL-INTERVAL", "COST", "STATUS")
- for idx, n := range nbrs {
- st := "enabled"
- if n.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), n.Address, n.Area, fmt.Sprintf("%d", n.PollInterval), fmt.Sprintf("%d", n.Cost), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify OSPF\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- addr := params["address"]
- if addr == "" {
- return ColorRed + "Usage: /routing ospf static-neighbor add address=<ip> [area=...] [poll-interval=...] [cost=...]\n" + ColorReset
- }
- area := params["area"]
- if area == "" {
- area = "Default"
- }
- poll := 120
- if pStr, ok := params["poll-interval"]; ok && pStr != "" {
- poll, _ = strconv.Atoi(pStr)
- }
- cost := 1
- if cStr, ok := params["cost"]; ok && cStr != "" {
- cost, _ = strconv.Atoi(cStr)
- }
- disabled := parseCLIEnableDisable(params["disabled"], false)
- snbr := dynamicrouting.OSPFStaticNeighbor{
- Address: addr,
- Area: area,
- PollInterval: poll,
- Cost: cost,
- Disabled: disabled,
- Comment: params["comment"],
- }
- if err := e.dynRoutingMgr.SaveOSPFStaticNeighbor(snbr); err != nil {
- return ColorRed + "Error saving static neighbor: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF static neighbor %s saved.\n", addr) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf static-neighbor set <number|address> [poll-interval=...] [cost=...]\n" + ColorReset
- }
- nbrs := e.dynRoutingMgr.ListOSPFStaticNeighbors()
- var targetNbr *dynamicrouting.OSPFStaticNeighbor
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nbrs) {
- targetNbr = &nbrs[idx]
- } else {
- for i := range nbrs {
- if nbrs[i].Address == target || nbrs[i].ID == target {
- targetNbr = &nbrs[i]
- break
- }
- }
- }
- if targetNbr == nil {
- return ColorRed + fmt.Sprintf("Static neighbor %q not found\n", target) + ColorReset
- }
- if v, ok := params["address"]; ok && v != "" {
- targetNbr.Address = v
- }
- if v, ok := params["area"]; ok && v != "" {
- targetNbr.Area = v
- }
- if v, ok := params["poll-interval"]; ok {
- targetNbr.PollInterval, _ = strconv.Atoi(v)
- }
- if v, ok := params["cost"]; ok {
- targetNbr.Cost, _ = strconv.Atoi(v)
- }
- if v, ok := params["disabled"]; ok {
- targetNbr.Disabled = parseCLIEnableDisable(v, false)
- }
- if v, ok := params["comment"]; ok {
- targetNbr.Comment = v
- }
- if err := e.dynRoutingMgr.SaveOSPFStaticNeighbor(*targetNbr); err != nil {
- return ColorRed + "Error updating static neighbor: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF static neighbor %s updated.\n", targetNbr.Address) + ColorReset
- case "remove", "rem", "delete":
- pos, params := parseArgs(parts[1:])
- target := ""
- if len(pos) > 0 {
- target = pos[0]
- } else if v, ok := params["numbers"]; ok {
- target = v
- }
- if target == "" {
- return ColorRed + "Usage: /routing ospf static-neighbor remove <number|address>\n" + ColorReset
- }
- nbrs := e.dynRoutingMgr.ListOSPFStaticNeighbors()
- idToDelete := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nbrs) {
- idToDelete = nbrs[idx].ID
- } else {
- for _, n := range nbrs {
- if n.Address == target || n.ID == target {
- idToDelete = n.ID
- break
- }
- }
- }
- if idToDelete == "" {
- return ColorRed + fmt.Sprintf("Static neighbor %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteOSPFStaticNeighbor(idToDelete); err != nil {
- return ColorRed + "Error deleting static neighbor: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("OSPF static neighbor %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf static-neighbor enable <number|address>\n" + ColorReset
- }
- return e.setOSPFStaticNeighborStateByTarget(pos[0], false)
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing ospf static-neighbor disable <number|address>\n" + ColorReset
- }
- return e.setOSPFStaticNeighborStateByTarget(pos[0], true)
- default:
- return ColorRed + "Usage: /routing ospf static-neighbor [print|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleOSPFNeighborLive(parts []string, user *auth.PublicUser) string {
- neighbors := e.dynRoutingMgr.GetOSPFNeighbors()
- tbl := NewTable("ROUTER-ID", "ADDRESS", "INTERFACE", "STATE", "PRIORITY", "DEAD-TIMER")
- for _, n := range neighbors {
- tbl.AddRow(n.NeighborID, n.Address, n.Interface, n.State, fmt.Sprintf("%d", n.Priority), n.DeadTimer)
- }
- return tbl.String()
- }
- func (e *Executor) handleOSPFLSA(parts []string, user *auth.PublicUser) string {
- lsas := e.dynRoutingMgr.GetOSPFLSA()
- tbl := NewTable("TYPE", "LS-ID", "ROUTER-ID", "AGE", "SEQUENCE")
- for _, l := range lsas {
- tbl.AddRow(l.Type, l.ID, l.Originator, fmt.Sprintf("%d", l.Age), l.Sequence)
- }
- return tbl.String()
- }
- func (e *Executor) handleOSPFRoute(parts []string, user *auth.PublicUser) string {
- routes := e.dynRoutingMgr.GetOSPFRoutes()
- tbl := NewTable("PREFIX", "NEXT-HOP", "INTERFACE", "COST", "TYPE")
- for _, r := range routes {
- tbl.AddRow(r.Prefix, r.NextHop, r.Interface, fmt.Sprintf("%d", r.Cost), r.RouteType)
- }
- return tbl.String()
- }
- func (e *Executor) setOSPFInstanceStateByTarget(target string, disabled bool) string {
- instances := e.dynRoutingMgr.ListOSPFInstances()
- id := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(instances) {
- id = instances[idx].ID
- } else {
- for _, inst := range instances {
- if strings.EqualFold(inst.Name, target) || inst.ID == target {
- id = inst.ID
- break
- }
- }
- }
- if id == "" {
- return ColorRed + fmt.Sprintf("OSPF instance %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.SetOSPFInstanceState(id, disabled); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- action := "enabled"
- if disabled {
- action = "disabled"
- }
- return ColorGreen + fmt.Sprintf("OSPF instance %s %s.\n", target, action) + ColorReset
- }
- func (e *Executor) setOSPFAreaStateByTarget(target string, disabled bool) string {
- areas := e.dynRoutingMgr.ListOSPFAreas()
- id := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(areas) {
- id = areas[idx].ID
- } else {
- for _, a := range areas {
- if strings.EqualFold(a.Name, target) || a.ID == target {
- id = a.ID
- break
- }
- }
- }
- if id == "" {
- return ColorRed + fmt.Sprintf("OSPF area %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.SetOSPFAreaState(id, disabled); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- action := "enabled"
- if disabled {
- action = "disabled"
- }
- return ColorGreen + fmt.Sprintf("OSPF area %s %s.\n", target, action) + ColorReset
- }
- func (e *Executor) setOSPFInterfaceStateByTarget(target string, disabled bool) string {
- ifaces := e.dynRoutingMgr.ListOSPFInterfaces()
- id := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(ifaces) {
- id = ifaces[idx].ID
- } else {
- for _, i := range ifaces {
- if i.ID == target || i.Interfaces == target || i.Interface == target {
- id = i.ID
- break
- }
- }
- }
- if id == "" {
- return ColorRed + fmt.Sprintf("OSPF interface template %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.SetOSPFInterfaceState(id, disabled); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- action := "enabled"
- if disabled {
- action = "disabled"
- }
- return ColorGreen + fmt.Sprintf("OSPF interface template %s %s.\n", target, action) + ColorReset
- }
- func (e *Executor) setOSPFStaticNeighborStateByTarget(target string, disabled bool) string {
- nbrs := e.dynRoutingMgr.ListOSPFStaticNeighbors()
- id := ""
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(nbrs) {
- id = nbrs[idx].ID
- } else {
- for _, n := range nbrs {
- if n.Address == target || n.ID == target {
- id = n.ID
- break
- }
- }
- }
- if id == "" {
- return ColorRed + fmt.Sprintf("Static neighbor %q not found\n", target) + ColorReset
- }
- if err := e.dynRoutingMgr.SetOSPFStaticNeighborState(id, disabled); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- action := "enabled"
- if disabled {
- action = "disabled"
- }
- return ColorGreen + fmt.Sprintf("Static neighbor %s %s.\n", target, action) + ColorReset
- }
- func (e *Executor) handleRoutingFilter(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return e.handleRoutingFilterRule([]string{"print"}, user)
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "rule", "rules":
- return e.handleRoutingFilterRule(parts[1:], user)
- case "community-list", "community-lists":
- return e.handleRoutingFilterCommunityList(parts[1:], user)
- case "as-list", "as-lists", "num-list", "num-lists":
- return e.handleRoutingFilterASList(parts[1:], user)
- case "print", "pr":
- return e.handleRoutingFilterRule([]string{"print"}, user)
- default:
- return ColorRed + "Usage: /routing filter [rule|community-list|num-list]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingFilterRule(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- rules := e.dynRoutingMgr.ListRoutingFilters()
- tbl := NewTable("#", "CHAIN", "ORDER", "RULE", "ACTION", "STATUS")
- for idx, r := range rules {
- st := "enabled"
- if r.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), r.Chain, fmt.Sprintf("%d", r.RuleOrder), r.Rule, r.Action, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify filters\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- chain := params["chain"]
- if chain == "" {
- return ColorRed + "Usage: /routing filter rule add chain=<name> [rule=\"...\"] [action=accept|discard|reject] [rule-order=N] [prefix=<cidr>]\n" + ColorReset
- }
- ruleText := params["rule"]
- action := params["action"]
- if action == "" {
- action = "accept"
- }
- order := 10
- if v, ok := params["rule-order"]; ok {
- fmt.Sscanf(v, "%d", &order)
- }
- r := dynamicrouting.RoutingFilterRule{
- Chain: chain,
- RuleOrder: order,
- Rule: ruleText,
- Action: action,
- Prefix: params["prefix"],
- PrefixLen: params["prefix-len"],
- }
- if err := e.dynRoutingMgr.SaveRoutingFilter(&r); err != nil {
- return ColorRed + "Error adding filter rule: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Routing filter rule added to chain %s.\n", chain) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing filter rule remove <#|id>\n" + ColorReset
- }
- target := pos[0]
- rules := e.dynRoutingMgr.ListRoutingFilters()
- var ruleID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(rules) {
- ruleID = rules[idx].ID
- } else {
- for _, r := range rules {
- if r.ID == target {
- ruleID = r.ID
- break
- }
- }
- }
- if ruleID == "" {
- return ColorRed + "Routing filter rule not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteRoutingFilter(ruleID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Routing filter rule %s removed.\n", target) + ColorReset
- case "enable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing filter rule enable <#|id>\n" + ColorReset
- }
- target := pos[0]
- rules := e.dynRoutingMgr.ListRoutingFilters()
- var ruleID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(rules) {
- ruleID = rules[idx].ID
- } else {
- ruleID = target
- }
- if err := e.dynRoutingMgr.SetRoutingFilterState(ruleID, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Routing filter rule %s enabled.\n", target) + ColorReset
- case "disable":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing filter rule disable <#|id>\n" + ColorReset
- }
- target := pos[0]
- rules := e.dynRoutingMgr.ListRoutingFilters()
- var ruleID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(rules) {
- ruleID = rules[idx].ID
- } else {
- ruleID = target
- }
- if err := e.dynRoutingMgr.SetRoutingFilterState(ruleID, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Routing filter rule %s disabled.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing filter rule [print|add|remove|enable|disable]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingFilterCommunityList(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- lists := e.dynRoutingMgr.ListCommunityLists()
- tbl := NewTable("#", "NAME", "COMMUNITIES", "STATUS")
- for idx, l := range lists {
- st := "enabled"
- if l.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), l.Name, strings.Join(l.Communities, ","), st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify filters\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- comms := params["communities"]
- if name == "" || comms == "" {
- return ColorRed + "Usage: /routing filter community-list add name=<name> communities=<c1,c2>\n" + ColorReset
- }
- cl := dynamicrouting.RoutingFilterCommunityList{
- Name: name,
- Communities: strings.Split(comms, ","),
- }
- if err := e.dynRoutingMgr.SaveCommunityList(&cl); err != nil {
- return ColorRed + "Error adding community-list: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Community list %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing filter community-list remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- lists := e.dynRoutingMgr.ListCommunityLists()
- var clID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(lists) {
- clID = lists[idx].ID
- } else {
- for _, l := range lists {
- if l.Name == target || l.ID == target {
- clID = l.ID
- break
- }
- }
- }
- if clID == "" {
- return ColorRed + "Community list not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteCommunityList(clID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Community list %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing filter community-list [print|add|remove]\n" + ColorReset
- }
- }
- func (e *Executor) handleRoutingFilterASList(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- lists := e.dynRoutingMgr.ListASLists()
- tbl := NewTable("#", "NAME", "RANGE", "STATUS")
- for idx, l := range lists {
- st := "enabled"
- if l.Disabled {
- st = "disabled"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), l.Name, l.Range, st)
- }
- return tbl.String()
- }
- if user.Role == auth.RoleRead {
- return ColorRed + "Permission denied: viewer role cannot modify filters\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- rng := params["range"]
- if name == "" || rng == "" {
- return ColorRed + "Usage: /routing filter as-list add name=<name> range=<65000-65500>\n" + ColorReset
- }
- al := dynamicrouting.RoutingFilterASList{
- Name: name,
- Range: rng,
- }
- if err := e.dynRoutingMgr.SaveASList(&al); err != nil {
- return ColorRed + "Error adding as-list: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("AS list %s added.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /routing filter as-list remove <#|name|id>\n" + ColorReset
- }
- target := pos[0]
- lists := e.dynRoutingMgr.ListASLists()
- var alID string
- if idx, err := strconv.Atoi(target); err == nil && idx >= 0 && idx < len(lists) {
- alID = lists[idx].ID
- } else {
- for _, l := range lists {
- if l.Name == target || l.ID == target {
- alID = l.ID
- break
- }
- }
- }
- if alID == "" {
- return ColorRed + "AS list not found: " + target + "\n" + ColorReset
- }
- if err := e.dynRoutingMgr.DeleteASList(alID); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("AS list %s removed.\n", target) + ColorReset
- default:
- return ColorRed + "Usage: /routing filter as-list [print|add|remove]\n" + ColorReset
- }
- }
- // ==================== TOOLS ====================
- func (e *Executor) handleTool(parts []string) string {
- if len(parts) == 0 {
- return ColorYellow + "Tools available: ping, traceroute, bandwidth-test, netwatch, ip-scan, torch\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "ping":
- return e.handlePing(parts[1:])
- case "traceroute":
- return e.handleTraceroute(parts[1:])
- case "bandwidth-test", "btest":
- return e.handleBandwidthTest(parts[1:])
- case "netwatch":
- return e.handleToolNetwatch(parts[1:])
- case "ip-scan":
- return e.handleToolIPScan(parts[1:])
- case "torch", "sniff":
- return e.handleToolTorch(parts[1:])
- default:
- return ColorRed + "Unknown tool: " + sub + "\n" + ColorReset
- }
- }
- func (e *Executor) handleToolNetwatch(parts []string) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- tbl := NewTable("HOST", "STATUS", "RTT", "SINCE")
- // Test ping to common gateways or local hosts if any
- hosts := []string{"1.1.1.1", "8.8.8.8"}
- for _, h := range hosts {
- cmd := exec.Command("ping", "-c", "1", "-W", "1", h)
- out, err := cmd.CombinedOutput()
- st := ColorGreen + "UP" + ColorReset
- rtt := "-"
- if err != nil {
- st = ColorRed + "DOWN" + ColorReset
- } else {
- for _, line := range strings.Split(string(out), "\n") {
- if strings.Contains(line, "time=") {
- idx := strings.Index(line, "time=")
- rtt = strings.Fields(line[idx+5:])[0] + "ms"
- break
- }
- }
- }
- tbl.AddRow(h, st, rtt, "running")
- }
- return tbl.String()
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- host := params["host"]
- if host == "" {
- return ColorRed + "Usage: /tool netwatch add host=<ip> [interval=10s] [timeout=1s]\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Netwatch monitor configured for %s.\n", host) + ColorReset
- default:
- return ColorRed + "Usage: /tool netwatch [print|add|remove]\n" + ColorReset
- }
- }
- func (e *Executor) handleToolIPScan(parts []string) string {
- _, params := parseArgs(parts)
- iface := params["interface"]
- if iface == "" && len(parts) > 0 && !strings.Contains(parts[0], "=") {
- iface = parts[0]
- }
- data, err := os.ReadFile("/proc/net/arp")
- if err != nil {
- return ColorRed + "Error reading ARP table: " + err.Error() + "\n" + ColorReset
- }
- tbl := NewTable("IP-ADDRESS", "MAC-ADDRESS", "INTERFACE", "STATUS")
- lines := strings.Split(string(data), "\n")
- count := 0
- for _, l := range lines[1:] {
- fields := strings.Fields(l)
- if len(fields) >= 6 {
- dev := fields[5]
- if iface != "" && dev != iface {
- continue
- }
- ip := fields[0]
- mac := fields[3]
- if mac == "00:00:00:00:00:00" {
- continue
- }
- tbl.AddRow(ip, mac, dev, "reachable")
- count++
- }
- }
- if count == 0 {
- return "No hosts found on " + iface + ".\n"
- }
- return tbl.String()
- }
- func (e *Executor) handleToolTorch(parts []string) string {
- _, params := parseArgs(parts)
- iface := params["interface"]
- if iface == "" && len(parts) > 0 && !strings.Contains(parts[0], "=") {
- iface = parts[0]
- }
- if iface == "" {
- ifaces, _ := e.netMgr.ListInterfaces()
- for _, ifc := range ifaces {
- if ifc.Name != "lo" {
- iface = ifc.Name
- break
- }
- }
- }
- if iface == "" {
- return ColorRed + "Usage: /tool torch interface=<iface>\n" + ColorReset
- }
- readStat := func(stat string) uint64 {
- p := filepath.Join("/sys/class/net", iface, "statistics", stat)
- b, err := os.ReadFile(p)
- if err != nil {
- return 0
- }
- var val uint64
- fmt.Sscanf(strings.TrimSpace(string(b)), "%d", &val)
- return val
- }
- rx1 := readStat("rx_bytes")
- tx1 := readStat("tx_bytes")
- rxp1 := readStat("rx_packets")
- txp1 := readStat("tx_packets")
- time.Sleep(500 * time.Millisecond)
- rx2 := readStat("rx_bytes")
- tx2 := readStat("tx_bytes")
- rxp2 := readStat("rx_packets")
- txp2 := readStat("tx_packets")
- rxRateKbps := float64(rx2-rx1) * 8.0 / 1000.0 / 0.5
- txRateKbps := float64(tx2-tx1) * 8.0 / 1000.0 / 0.5
- rxPps := float64(rxp2-rxp1) / 0.5
- txPps := float64(txp2-txp1) / 0.5
- tbl := NewTable("INTERFACE", "RX-RATE", "TX-RATE", "RX-PPS", "TX-PPS")
- tbl.AddRow(iface, fmt.Sprintf("%.1f kbps", rxRateKbps), fmt.Sprintf("%.1f kbps", txRateKbps), fmt.Sprintf("%.0f", rxPps), fmt.Sprintf("%.0f", txPps))
- return tbl.String()
- }
- func (e *Executor) handlePing(args []string) string {
- if len(args) == 0 {
- return ColorRed + "Usage: ping <host> [count=4]\n" + ColorReset
- }
- pos, params := parseArgs(args)
- if len(pos) == 0 {
- return ColorRed + "Missing host target for ping\n" + ColorReset
- }
- target := pos[0]
- count := "4"
- if c, ok := params["count"]; ok {
- count = c
- }
- cmd := exec.Command("ping", "-c", count, target)
- out, err := cmd.CombinedOutput()
- if err != nil && len(out) == 0 {
- return ColorRed + "Ping execution failed: " + err.Error() + ColorReset + "\n"
- }
- return string(out)
- }
- func (e *Executor) handleTraceroute(args []string) string {
- if len(args) == 0 {
- return ColorRed + "Usage: traceroute <host>\n" + ColorReset
- }
- pos, _ := parseArgs(args)
- if len(pos) == 0 {
- return ColorRed + "Missing host target for traceroute\n" + ColorReset
- }
- target := pos[0]
- cmd := exec.Command("traceroute", "-n", "-m", "15", target)
- out, err := cmd.CombinedOutput()
- if err != nil {
- cmdFallback := exec.Command("tracepath", "-n", target)
- fbOut, fbErr := cmdFallback.CombinedOutput()
- if fbErr == nil {
- return string(fbOut)
- }
- return ColorRed + "Traceroute failed: " + err.Error() + "\n" + string(out) + ColorReset + "\n"
- }
- return string(out)
- }
- func (e *Executor) handleBandwidthTest(args []string) string {
- if e.bwtestMgr == nil {
- return ColorRed + "Bandwidth test manager not available.\n" + ColorReset
- }
- pos, params := parseArgs(args)
- if len(pos) == 0 {
- return ColorRed + "Usage: /tool bandwidth-test <target-ip> [direction=both|transmit|receive] [protocol=udp|tcp] [duration=5s]\n" + ColorReset
- }
- target := pos[0]
- dir := "both"
- if d, ok := params["direction"]; ok {
- dir = d
- }
- switch dir {
- case "transmit":
- dir = "tx"
- case "receive":
- dir = "rx"
- case "tx", "rx", "both":
- default:
- return cliError(fmt.Errorf("direction must be both, tx/transmit, or rx/receive"))
- }
- proto := "udp"
- if p, ok := params["protocol"]; ok {
- proto = p
- }
- if proto != "udp" && proto != "tcp" {
- return cliError(fmt.Errorf("protocol must be udp or tcp"))
- }
- dur := 5 * time.Second
- if dStr, ok := params["duration"]; ok {
- if dVal, err := time.ParseDuration(dStr); err == nil && dVal >= time.Second {
- dur = dVal
- } else {
- return cliError(fmt.Errorf("duration must be at least 1s"))
- }
- }
- req := bwtest.SessionRequest{
- Target: target,
- Direction: dir,
- Protocol: proto,
- Duration: int(dur.Seconds()),
- }
- ctx, cancel := context.WithTimeout(context.Background(), dur+8*time.Second)
- defer cancel()
- if err := e.bwtestMgr.StartTest(ctx, req); err != nil {
- return ColorRed + "Failed to initiate bandwidth test: " + err.Error() + ColorReset + "\n"
- }
- st := e.bwtestMgr.GetTestStatus()
- ticker := time.NewTicker(100 * time.Millisecond)
- defer ticker.Stop()
- for st.Running {
- select {
- case <-ctx.Done():
- e.bwtestMgr.StopTest()
- return cliError(fmt.Errorf("bandwidth test did not finish before the deadline; stop requested"))
- case <-ticker.C:
- st = e.bwtestMgr.GetTestStatus()
- }
- }
- if st.Error != "" {
- return cliError(fmt.Errorf("bandwidth test failed: %s", st.Error))
- }
- out := fmt.Sprintf("Bandwidth test to %s completed:\n", target)
- if st.Summary != nil {
- out += fmt.Sprintf(" Tx Avg : %.2f Mbps\n", st.Summary.AvgTxMbps)
- out += fmt.Sprintf(" Rx Avg : %.2f Mbps\n", st.Summary.AvgRxMbps)
- out += fmt.Sprintf(" Loss : %.1f%%\n", st.Summary.AvgLossPct)
- out += fmt.Sprintf(" Jitter : %.2f ms\n", st.Summary.AvgJitterMs)
- } else if st.CurrentSample != nil {
- out += fmt.Sprintf(" Tx Rate : %.2f Mbps\n", st.CurrentSample.TxMbps)
- out += fmt.Sprintf(" Rx Rate : %.2f Mbps\n", st.CurrentSample.RxMbps)
- } else {
- out += " Test finished.\n"
- }
- return out
- }
- // ==================== USER MANAGEMENT ====================
- func (e *Executor) handleUser(parts []string, actor *auth.PublicUser) string {
- if e.authMgr == nil {
- return ColorRed + "Auth manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- users := e.authMgr.ListUsers()
- tbl := NewTable("FLAGS", "NAME", "ROLE", "LAST-LOGIN", "COMMENT")
- for _, u := range users {
- flags := ""
- if u.Disabled {
- flags += "X"
- } else {
- flags += " "
- }
- lastLogin := "never"
- if !u.LastLoginAt.IsZero() {
- lastLogin = u.LastLoginAt.Format("Jan/02 15:04:05")
- }
- tbl.AddRow(flags, u.Username, string(u.Role), lastLogin, u.Comment)
- }
- return tbl.String()
- }
- if actor.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to manage users\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- pwd := params["password"]
- roleStr := params["role"]
- if roleStr == "" {
- roleStr = "admin"
- }
- comment := params["comment"]
- if name == "" || pwd == "" {
- return ColorRed + "Usage: /user add name=<username> password=<pwd> [role=admin|operator|viewer] [comment=...]\n" + ColorReset
- }
- req := auth.CreateUserRequest{
- Username: name,
- Password: pwd,
- Role: auth.Role(roleStr),
- Comment: comment,
- }
- if err := e.authMgr.CreateUser(req); err != nil {
- return ColorRed + "Error creating user: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("User %s created.\n", name) + ColorReset
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /user remove <username>\n" + ColorReset
- }
- name := pos[0]
- if err := e.authMgr.DeleteUser(name, actor.Username); err != nil {
- return ColorRed + "Error removing user: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("User %s removed.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /user set <username> [password=...] [role=...] [disabled=yes|no]\n" + ColorReset
- }
- name := pos[0]
- pub, err := e.authMgr.GetPublicUser(name)
- if err != nil || pub == nil {
- return ColorRed + "User not found\n" + ColorReset
- }
- role := pub.Role
- if rStr, ok := params["role"]; ok {
- role = auth.Role(rStr)
- }
- dis := pub.Disabled
- if dStr, ok := params["disabled"]; ok {
- dis = (strings.ToLower(dStr) == "yes" || strings.ToLower(dStr) == "true")
- }
- cmt := pub.Comment
- if c, ok := params["comment"]; ok {
- cmt = c
- }
- req := auth.UpdateUserRequest{
- Role: role,
- Disabled: dis,
- Comment: cmt,
- }
- if err := e.authMgr.UpdateUser(name, req); err != nil {
- return ColorRed + "Error updating user: " + err.Error() + ColorReset + "\n"
- }
- if pwd, ok := params["password"]; ok && pwd != "" {
- if err := e.authMgr.ChangePassword(name, "", pwd, true); err != nil {
- return ColorRed + "Error resetting password: " + err.Error() + ColorReset + "\n"
- }
- }
- return ColorGreen + fmt.Sprintf("User %s updated.\n", name) + ColorReset
- default:
- return ColorRed + "Usage: /user [print|add|remove|set]\n" + ColorReset
- }
- }
- // ==================== SYSTEM ====================
- func (e *Executor) handleSystem(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 {
- return ColorYellow + "System commands: resource print, reboot, identity print, identity set name=...\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "resource":
- st, err := system.GetStatus()
- if err != nil {
- return ColorRed + "Error retrieving system resources: " + err.Error() + ColorReset + "\n"
- }
- freeMemMB := uint64(0)
- if st.MemTotalMB > st.MemUsedMB {
- freeMemMB = st.MemTotalMB - st.MemUsedMB
- }
- out := " uptime: " + formatUptime(int64(st.UptimeSeconds)) + "\n"
- out += fmt.Sprintf(" version: DebTik 1.0 (Kernel %s)\n", st.Kernel)
- out += fmt.Sprintf(" free-memory: %d MiB\n", freeMemMB)
- out += fmt.Sprintf(" total-memory: %d MiB\n", st.MemTotalMB)
- out += fmt.Sprintf(" cpu-count: %d\n", st.CPUCores)
- out += fmt.Sprintf(" cpu-load: %.1f%%\n", st.CPUPct)
- out += fmt.Sprintf(" os: %s\n", st.OS)
- return out
- case "identity":
- if len(parts) > 1 && parts[1] == "set" {
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- name := params["name"]
- if name == "" {
- return ColorRed + "Usage: /system identity set name=<new-name>\n" + ColorReset
- }
- if err := exec.Command("hostnamectl", "set-hostname", name).Run(); err != nil {
- return ColorRed + "Error setting identity: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("System identity set to %q.\n", name) + ColorReset
- }
- st, _ := system.GetStatus()
- h := "DebTik"
- if st != nil && st.Hostname != "" {
- h = st.Hostname
- }
- return fmt.Sprintf(" name: %s\n", h)
- case "reboot":
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to reboot\n" + ColorReset
- }
- go func() {
- time.Sleep(1 * time.Second)
- _ = exec.Command("systemctl", "reboot").Run()
- }()
- return ColorYellow + "System reboot initiated! Connection will terminate.\n" + ColorReset
- case "backup":
- if e.backupMgr == nil {
- e.backupMgr = backup.NewManager("", "", "", nil)
- }
- if len(parts) < 2 {
- return ColorYellow + "Usage: /system backup [save [name=...] [comment=...]] | [load name=...] | [print]\n" + ColorReset
- }
- action := parts[1]
- switch action {
- case "save":
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- name := params["name"]
- comment := params["comment"]
- savedPath, manifest, err := e.backupMgr.SaveBackup(name, comment)
- if err != nil {
- return ColorRed + "Error creating backup: " + err.Error() + ColorReset + "\n"
- }
- fi, _ := os.Stat(savedPath)
- size := int64(0)
- if fi != nil {
- size = fi.Size()
- }
- return ColorGreen + fmt.Sprintf("Configuration backup saved successfully:\n File: %s\n Size: %d bytes\n Hostname: %s\n", filepath.Base(savedPath), size, manifest.Hostname) + ColorReset
- case "print":
- backups, err := e.backupMgr.ListLocalBackups()
- if err != nil {
- return ColorRed + "Error listing backups: " + err.Error() + ColorReset + "\n"
- }
- if len(backups) == 0 {
- return "No backup archives found.\n"
- }
- b := strings.Builder{}
- b.WriteString(ColorCyan + fmt.Sprintf("%-3s %-42s %-10s %-20s %s\n", "#", "NAME", "SIZE", "CREATED", "COMMENT") + ColorReset)
- for i, bk := range backups {
- sizeStr := fmt.Sprintf("%d B", bk.Size)
- if bk.Size >= 1024*1024 {
- sizeStr = fmt.Sprintf("%.1f MB", float64(bk.Size)/(1024*1024))
- } else if bk.Size >= 1024 {
- sizeStr = fmt.Sprintf("%.1f KB", float64(bk.Size)/1024)
- }
- b.WriteString(fmt.Sprintf("%-3d %-42s %-10s %-20s %s\n",
- i,
- bk.Filename,
- sizeStr,
- bk.ModTime.Format("2006-01-02 15:04:05"),
- bk.Comment,
- ))
- }
- return b.String()
- case "load":
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to restore configuration\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- name := params["name"]
- if name == "" {
- return ColorRed + "Usage: /system backup load name=<backup-filename.zip>\n" + ColorReset
- }
- fullPath, err := e.backupMgr.GetLocalBackup(name)
- if err != nil {
- return ColorRed + "Backup file not found: " + err.Error() + ColorReset + "\n"
- }
- f, err := os.Open(fullPath)
- if err != nil {
- return ColorRed + "Cannot read backup file: " + err.Error() + ColorReset + "\n"
- }
- defer f.Close()
- fi, err := f.Stat()
- if err != nil {
- return ColorRed + "Cannot stat backup file: " + err.Error() + ColorReset + "\n"
- }
- res, err := e.backupMgr.RestoreBackup(f, fi.Size())
- if err != nil {
- return ColorRed + "Restore failed: " + err.Error() + ColorReset + "\n"
- }
- e.backupMgr.TriggerRestart(1 * time.Second)
- return ColorGreen + fmt.Sprintf("Configuration restored successfully from %s.\nAutomatic fallback saved: %s\nDebTik controller is reloading now...\n", name, filepath.Base(res.SafetyBackup)) + ColorReset
- case "remove", "rem", "delete":
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- _, params := parseArgs(parts[2:])
- name := params["name"]
- if name == "" && len(parts) > 2 {
- name = parts[2]
- }
- if name == "" {
- return ColorRed + "Usage: /system backup remove name=<backup-filename.zip>\n" + ColorReset
- }
- fullPath, err := e.backupMgr.GetLocalBackup(name)
- if err != nil {
- return ColorRed + "Backup file not found: " + err.Error() + "\n" + ColorReset
- }
- if err := os.Remove(fullPath); err != nil {
- return ColorRed + "Error removing backup: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Backup %s removed.\n", name) + ColorReset
- default:
- return ColorRed + "Unknown backup action: " + action + ". Use save, load, remove, or print.\n" + ColorReset
- }
- case "shutdown", "poweroff":
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to shutdown\n" + ColorReset
- }
- go func() {
- time.Sleep(1 * time.Second)
- _ = exec.Command("systemctl", "poweroff").Run()
- }()
- return ColorYellow + "System shutdown initiated! Goodbye.\n" + ColorReset
- case "package", "upgrade":
- version := release.Version()
- if version == "" {
- version = "1.8.8"
- }
- if len(parts) > 1 && (parts[1] == "update" || parts[1] == "check") {
- if len(parts) > 2 && (parts[2] == "check" || parts[2] == "check-for-updates") {
- return ColorGreen + fmt.Sprintf("Installed Version: %s\nChannel: stable\nStatus: System is up to date.\n", version) + ColorReset
- }
- if len(parts) > 2 && (parts[2] == "install" || parts[2] == "download") {
- return ColorGreen + fmt.Sprintf("DebTik %s is already the latest version.\n", version) + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Installed Version: %s\nChannel: stable\nStatus: System is up to date.\n", version) + ColorReset
- }
- tbl := NewTable("NAME", "VERSION", "BUILD-TIME", "STATUS")
- tbl.AddRow("debtik-router", version, time.Now().Format("2006-01-02"), "installed")
- return tbl.String()
- default:
- return ColorRed + "Unknown system subcommand: " + sub + "\n" + ColorReset
- }
- }
- func (e *Executor) showHelp() string {
- b := strings.Builder{}
- b.WriteString(ColorCyan + ColorBold + "DebTik CLI Command Directory:\n" + ColorReset)
- b.WriteString(" /interface - Network interface manager (print, enable, disable, set)\n")
- b.WriteString(" /interface vlan - VLAN virtual interfaces (print, add, remove)\n")
- b.WriteString(" /interface bridge - L2 Bridge and ports (print, add, remove, port)\n")
- b.WriteString(" /interface aerotun - Encrypted tunnels (print detail=yes, add, set, enable, disable, start, stop, remove, genkey)\n")
- b.WriteString(" /interface wireguard - WireGuard tunnels & peers (print, add, peers)\n")
- b.WriteString(" /interface eoip - Ethernet over IP GRE tunnels (print, add, remove)\n")
- b.WriteString(" /interface vxlan - VXLAN overlay tunnels & VTEPs (print, add, set, remove, enable, disable, vteps)\n")
- b.WriteString(" /interface vrrp - Virtual Router Redundancy Protocol (print, add, remove)\n")
- b.WriteString(" /interface pppoe-client - PPPoE WAN Client sessions (print, add, connect)\n")
- b.WriteString(" /interface l2tp-server - L2TP Server configuration (server print, server set)\n")
- b.WriteString(" /interface pptp-server - PPTP Server configuration (server print, server set)\n")
- b.WriteString(" /interface pppoe-server - PPPoE Access Concentrator Server (server print, server set)\n")
- b.WriteString(" /ppp - PPP Subscribers & Profiles manager\n")
- b.WriteString(" /ppp profile - PPP Profiles & IP pools (print, add, set, remove)\n")
- b.WriteString(" /ppp secret - PPP Subscriber accounts (print, add, set, remove, enable, disable)\n")
- b.WriteString(" /ppp active - Active PPP sessions (print, remove)\n")
- b.WriteString(" /ip\n")
- b.WriteString(" /ip address - IP address assignments (print, add, remove)\n")
- b.WriteString(" /ip route - Static routes & tables (print, add, remove, rule)\n")
- b.WriteString(" /ip dhcp-client - DHCP client on WAN links (print, add, renew, release)\n")
- b.WriteString(" /ip dhcp-server - Local DHCP server & leases (print, add, lease, network)\n")
- b.WriteString(" /ip dns - Router DNS resolver (print, set, cache flush)\n")
- b.WriteString(" /ip firewall - Netfilter / iptables firewall\n")
- b.WriteString(" /ip firewall filter - Traffic filtering rules (print, add, remove)\n")
- b.WriteString(" /ip firewall nat - Source & destination NAT rules (print, add, remove)\n")
- b.WriteString(" /ip firewall mangle - Packet marking and QoS (print, add, remove)\n")
- b.WriteString(" /ip firewall address-list - Dynamic & static address groups (print, add, remove)\n")
- b.WriteString(" /ip service - Management services: Telnet, SSH, Web (print, enable, disable, set)\n")
- b.WriteString(" /ip reverse-proxy - HTTP/HTTPS Reverse Proxy & Path Router (print, add, set, remove, enable, disable)\n")
- b.WriteString(" /ip sdwan - Software-Defined WAN multi-link aggregation (print, status, add, set, remove, enable, disable, member)\n")
- b.WriteString(" /routing - Dynamic routing engine\n")
- b.WriteString(" /routing bgp - BGP instance, peers, networks, routes (instance, peer, network, routes)\n")
- b.WriteString(" /routing ospf - OSPF areas, interfaces, neighbors (instance, area, area-range, interface, neighbor)\n")
- b.WriteString(" /routing rip - RIP routing protocol (instance, interface, neighbor, routes)\n")
- b.WriteString(" /routing bfd - Bidirectional Forwarding Detection (instance, interface, peer, session)\n")
- b.WriteString(" /routing babel - Babel mesh routing protocol (instance, interface, neighbor, routes)\n")
- b.WriteString(" /routing radv - IPv6 Router Advertisements (instance, interface)\n")
- b.WriteString(" /routing filter - Route filtering policies (rule, community-list, as-list)\n")
- b.WriteString(" /tool - Diagnostics & performance testing\n")
- b.WriteString(" /tool ping <host> - ICMP ping test\n")
- b.WriteString(" /tool traceroute <host> - Route trace analysis\n")
- b.WriteString(" /tool bandwidth-test <host> - High performance throughput & jitter test\n")
- b.WriteString(" /tool netwatch - Network host monitor\n")
- b.WriteString(" /tool ip-scan - Subnet neighbor scanner\n")
- b.WriteString(" /tool torch - Real-time traffic monitor\n")
- b.WriteString(" /log - System activity logs (print [limit=N] [where=filter])\n")
- b.WriteString(" /user - Local user accounts (print, add, remove, set)\n")
- b.WriteString(" /system - Router hardware and core controls\n")
- b.WriteString(" /system resource print - CPU, RAM, disk, uptime metrics\n")
- b.WriteString(" /system identity - Hostname identity (print, set name=...)\n")
- b.WriteString(" /system backup - Configuration backups (save, load, remove, print)\n")
- b.WriteString(" /system package - System version & updates (print, update check)\n")
- b.WriteString(" /system reboot - Safe system restart\n")
- b.WriteString(" /system shutdown - Safe system power off\n")
- b.WriteString(" quit / exit - Disconnect session\n")
- return b.String()
- }
- // ==================== CERTIFICATES (/certificate) ====================
- func (e *Executor) handleCertificate(parts []string, user *auth.PublicUser) string {
- if e.certMgr == nil {
- return ColorRed + "Certificates manager not available.\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- list := e.certMgr.ListCertificates()
- tbl := NewTable("#", "NAME", "COMMON-NAME", "TYPE", "ISSUER", "EXPIRES", "SANS")
- for idx, c := range list {
- tStr := ColorGreen + c.Type + ColorReset
- if c.IsCA {
- tStr = ColorCyan + "ca" + ColorReset
- }
- expStr := c.ValidTo.Format("Jan/02/2006")
- if time.Now().After(c.ValidTo) {
- expStr = ColorRed + expStr + " (EXPIRED)" + ColorReset
- }
- sansStr := strings.Join(c.SANs, ",")
- if sansStr == "" {
- sansStr = "-"
- }
- tbl.AddRow(fmt.Sprintf("%d", idx), c.Name, c.CommonName, tStr, c.Issuer, expStr, sansStr)
- }
- return tbl.String()
- }
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required to manage certificates\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add-ca":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- cn := params["common-name"]
- if name == "" || cn == "" {
- return ColorRed + "Usage: /certificate add-ca name=<name> common-name=<cn> [days=<days>] [key-type=RSA-2048|RSA-4096|ECDSA-P256]\n" + ColorReset
- }
- days := 3650
- if dStr, ok := params["days"]; ok {
- if dVal, err := strconv.Atoi(dStr); err == nil && dVal > 0 {
- days = dVal
- }
- }
- keyType := params["key-type"]
- if keyType == "" {
- keyType = "RSA-2048"
- }
- info, err := e.certMgr.CreateCA(certs.CreateCARequest{
- Name: name,
- CommonName: cn,
- Days: days,
- KeyType: keyType,
- Comment: params["comment"],
- })
- if err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Certificate Authority %q created successfully.\n", info.Name) + ColorReset
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- cn := params["common-name"]
- if name == "" || cn == "" {
- return ColorRed + "Usage: /certificate add name=<name> common-name=<cn> [ca=<ca_name>] [sans=<san1,san2>] [days=<days>] [key-type=...]\n" + ColorReset
- }
- days := 365
- if dStr, ok := params["days"]; ok {
- if dVal, err := strconv.Atoi(dStr); err == nil && dVal > 0 {
- days = dVal
- }
- }
- var sans []string
- if sStr, ok := params["sans"]; ok {
- for _, s := range strings.Split(sStr, ",") {
- if s = strings.TrimSpace(s); s != "" {
- sans = append(sans, s)
- }
- }
- }
- keyType := params["key-type"]
- if keyType == "" {
- keyType = "RSA-2048"
- }
- info, err := e.certMgr.CreateCertificate(certs.CreateCertRequest{
- Name: name,
- CommonName: cn,
- CAName: params["ca"],
- SANs: sans,
- Days: days,
- KeyType: keyType,
- IsServer: true,
- Comment: params["comment"],
- })
- if err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Certificate %q created successfully (Issuer: %s).\n", info.Name, info.Issuer) + ColorReset
- case "issue-letsencrypt":
- _, params := parseArgs(parts[1:])
- domain := params["domain"]
- email := params["email"]
- if domain == "" || email == "" {
- return ColorRed + "Usage: /certificate issue-letsencrypt domain=<domain> email=<email> [name=<name>] [staging=yes|no]\n" + ColorReset
- }
- name := params["name"]
- if name == "" {
- name = domain
- }
- staging := strings.ToLower(params["staging"]) == "yes" || strings.ToLower(params["staging"]) == "true"
- ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
- defer cancel()
- info, err := e.certMgr.IssueLetsEncrypt(ctx, certs.LetsEncryptRequest{
- Name: name,
- Domain: domain,
- Email: email,
- Staging: staging,
- Comment: params["comment"],
- })
- if err != nil {
- return ColorRed + "Let's Encrypt Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Let's Encrypt certificate %q issued successfully!\n", info.Name) + ColorReset
- case "export":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /certificate export <name>\n" + ColorReset
- }
- certPEM, keyPEM, err := e.certMgr.ExportPEM(pos[0])
- if err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return fmt.Sprintf("----- CERTIFICATE -----\n%s\n----- PRIVATE KEY -----\n%s\n", certPEM, keyPEM)
- case "remove", "rem", "delete":
- pos, _ := parseArgs(parts[1:])
- if len(pos) == 0 {
- return ColorRed + "Usage: /certificate remove <name>\n" + ColorReset
- }
- name := pos[0]
- if e.servicesMgr != nil {
- if sslSvc, ok := e.servicesMgr.GetService("www-ssl"); ok {
- if strings.EqualFold(sslSvc.Certificate, name) && !sslSvc.Disabled {
- return ColorRed + "Cannot remove certificate: actively used by enabled www-ssl service\n" + ColorReset
- }
- }
- }
- if err := e.certMgr.DeleteCertificate(name); err != nil {
- return ColorRed + "Error: " + err.Error() + ColorReset + "\n"
- }
- return ColorGreen + fmt.Sprintf("Certificate %s removed.\n", name) + ColorReset
- default:
- return ColorRed + "Usage: /certificate [print|add-ca|add|issue-letsencrypt|export|remove]\n" + ColorReset
- }
- }
- func formatUptime(seconds int64) string {
- d := seconds / 86400
- h := (seconds % 86400) / 3600
- m := (seconds % 3600) / 60
- s := seconds % 60
- if d > 0 {
- return fmt.Sprintf("%dd %02dh %02dm %02ds", d, h, m, s)
- }
- return fmt.Sprintf("%02dh %02dm %02ds", h, m, s)
- }
- func formatBytes(bytes int64) string {
- const unit = 1024
- if bytes < unit {
- return fmt.Sprintf("%d B", bytes)
- }
- div, exp := int64(unit), 0
- for n := bytes / unit; n >= unit; n /= unit {
- div *= unit
- exp++
- }
- return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
- }
- func (e *Executor) handleIPReverseProxy(parts []string, user *auth.PublicUser) string {
- if e.proxyMgr == nil {
- return ColorRed + "Reverse proxy service not available\n" + ColorReset
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- rules := e.proxyMgr.ListRules()
- cfg := e.proxyMgr.GetConfig()
- var out strings.Builder
- stateStr := ColorGreen + "enabled" + ColorReset
- if !cfg.Enabled {
- stateStr = ColorRed + "disabled" + ColorReset
- }
- out.WriteString(fmt.Sprintf("Flags: X - disabled\nReverse Proxy Master: %s | HTTP Port: %d | HTTPS Port: %d\n", stateStr, cfg.HTTPPort, cfg.HTTPSPort))
- tbl := NewTable("Flags", "Name", "Domains", "Path", "Target", "Proto", "SSL Cert", "Requests")
- for _, r := range rules {
- flag := " "
- if !r.Enabled {
- flag = "X"
- }
- doms := strings.Join(r.Domains, ",")
- if doms == "" {
- doms = "*"
- }
- cert := r.Certificate
- if cert == "" {
- cert = "none"
- }
- reqCount := fmt.Sprintf("%d", r.Stats.Requests)
- tbl.AddRow(flag, r.Name, doms, r.Path, r.TargetURL, r.Protocol, cert, reqCount)
- }
- out.WriteString(tbl.String())
- return out.String()
- }
- if parts[0] == "status" {
- cfg := e.proxyMgr.GetConfig()
- var totalReq uint64
- for _, r := range cfg.Rules {
- totalReq += r.Stats.Requests
- }
- return fmt.Sprintf("Reverse Proxy Status:\n Enabled: %v\n HTTP Port: %d\n HTTPS Port: %d\n Rules Count: %d\n Total Requests: %d\n Default Cert: %s\n",
- cfg.Enabled, cfg.HTTPPort, cfg.HTTPSPort, len(cfg.Rules), totalReq, cfg.DefaultCert)
- }
- if user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- target := params["target"]
- if target == "" {
- target = params["target-url"]
- }
- if name == "" || target == "" {
- return ColorRed + "Usage: /ip reverse-proxy add name=<name> target=<url> [domains=d1,d2] [path=<path>] [protocol=http|https|both] [certificate=<cert>] [strip-path=yes|no] [http-to-https=yes|no]\n" + ColorReset
- }
- var domains []string
- if dStr, ok := params["domains"]; ok {
- for _, d := range strings.Split(dStr, ",") {
- if d = strings.TrimSpace(d); d != "" {
- domains = append(domains, d)
- }
- }
- }
- pfx := params["path"]
- if pfx == "" {
- pfx = "/"
- }
- proto := params["protocol"]
- if proto == "" {
- proto = "both"
- }
- stripPath := (params["strip-path"] == "yes" || params["strip-path"] == "true")
- httpToHttps := (params["http-to-https"] == "yes" || params["http-to-https"] == "true")
- insecureTLS := (params["insecure-tls"] == "yes" || params["insecure-tls"] == "true")
- rule, err := e.proxyMgr.SaveRule(&reverseproxy.Rule{
- Name: name,
- Enabled: true,
- Domains: domains,
- Path: pfx,
- TargetURL: target,
- StripPath: stripPath,
- Protocol: proto,
- Certificate: params["certificate"],
- HTTPToHTTPS: httpToHttps,
- InsecureTLS: insecureTLS,
- Comment: params["comment"],
- })
- if err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Proxy rule %q (ID: %s) added successfully.\n", rule.Name, rule.ID) + ColorReset
- case "remove", "delete":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip reverse-proxy remove <id>\n" + ColorReset
- }
- id := parts[1]
- if err := e.proxyMgr.DeleteRule(id); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Rule %q removed.\n", id) + ColorReset
- case "enable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip reverse-proxy enable <id>\n" + ColorReset
- }
- id := parts[1]
- if err := e.proxyMgr.SetRuleState(id, true); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Rule %q enabled.\n", id) + ColorReset
- case "disable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip reverse-proxy disable <id>\n" + ColorReset
- }
- id := parts[1]
- if err := e.proxyMgr.SetRuleState(id, false); err != nil {
- return ColorRed + "Error: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Rule %q disabled.\n", id) + ColorReset
- default:
- return ColorRed + "Usage: /ip reverse-proxy [print|status|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- // ==================== SD-WAN ====================
- func (e *Executor) handleIPSDWAN(parts []string, user *auth.PublicUser) string {
- if e.sdwanMgr == nil {
- return ColorRed + "SD-WAN service not available\n" + ColorReset
- }
- if len(parts) > 0 && (strings.ToLower(parts[0]) == "member" || strings.ToLower(parts[0]) == "members") {
- return e.handleSDWANMember(parts[1:], user)
- }
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" || parts[0] == "show" {
- groups := e.sdwanMgr.List()
- var out strings.Builder
- telemetry := e.sdwanMgr.GetTelemetrySummary()
- out.WriteString(fmt.Sprintf("SD-WAN Groups: %d active / %d total | Members: %d healthy / %d total\n",
- telemetry.ActiveGroups, telemetry.TotalGroups, telemetry.HealthyMembers, telemetry.TotalMembers))
- tbl := NewTable("Flags", "Name", "Mode", "Members", "Probe(s)", "Auto-NAT", "Comment")
- for _, g := range groups {
- flag := " "
- if g.Disabled {
- flag = "X"
- } else if g.Running {
- flag = "R"
- }
- autoNat := "no"
- if g.AutoNAT {
- autoNat = "yes"
- }
- tbl.AddRow(flag, g.Name, string(g.Mode), fmt.Sprintf("%d", len(g.Members)), fmt.Sprintf("%ds", g.ProbeInterval), autoNat, g.Comment)
- }
- out.WriteString(tbl.String())
- return out.String()
- }
- if parts[0] == "status" {
- t := e.sdwanMgr.GetTelemetrySummary()
- var out strings.Builder
- out.WriteString(ColorCyan + ColorBold + "DebTik SD-WAN Telemetry Status:\n" + ColorReset)
- out.WriteString(fmt.Sprintf(" Total Groups: %d\n", t.TotalGroups))
- out.WriteString(fmt.Sprintf(" Active Groups: %d\n", t.ActiveGroups))
- out.WriteString(fmt.Sprintf(" Total Members: %d\n", t.TotalMembers))
- out.WriteString(fmt.Sprintf(" Healthy Links: %d\n", t.HealthyMembers))
- out.WriteString(fmt.Sprintf(" Degraded Links: %d\n", t.DegradedMembers))
- out.WriteString(fmt.Sprintf(" Down Links: %d\n\n", t.DownMembers))
- groups := e.sdwanMgr.List()
- for _, g := range groups {
- out.WriteString(fmt.Sprintf("Group: %s [%s] (Members: %d)\n", g.Name, g.Mode, len(g.Members)))
- for _, m := range g.Members {
- statusCol := ColorGreen + string(m.Status) + ColorReset
- if m.Status == sdwan.StatusDegraded {
- statusCol = ColorYellow + string(m.Status) + ColorReset
- } else if m.Status == sdwan.StatusDown {
- statusCol = ColorRed + string(m.Status) + ColorReset
- } else if m.Status == sdwan.StatusDisabled {
- statusCol = ColorDim + string(m.Status) + ColorReset
- }
- out.WriteString(fmt.Sprintf(" - %s (GW: %s) -> Status: %s | Latency: %.1fms | Loss: %.1f%% | Jitter: %.1fms | Table: %d\n",
- m.Interface, m.Gateway, statusCol, m.LiveLatency, m.PacketLoss, m.Jitter, m.TableID))
- }
- }
- return out.String()
- }
- if user != nil && user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- name := params["name"]
- if name == "" {
- return ColorRed + "Usage: /ip sdwan add name=<name> [mode=weighted|pcc|lowest-latency|lowest-loss|failover] [auto-nat=yes|no] [probe-interval=3] [comment=\"...\"]\n" + ColorReset
- }
- mode := sdwan.SDWANMode(params["mode"])
- if mode == "" {
- mode = sdwan.ModeWeighted
- }
- autoNat := (params["auto-nat"] == "yes" || params["auto-nat"] == "true")
- probeInt := 3
- if pStr, ok := params["probe-interval"]; ok {
- if v, err := strconv.Atoi(pStr); err == nil && v > 0 {
- probeInt = v
- }
- }
- group := &sdwan.SDWANGroup{
- Name: name,
- Mode: mode,
- HashFields: params["hash-fields"],
- AutoNAT: autoNat,
- ProbeInterval: probeInt,
- Comment: params["comment"],
- Disabled: (params["disabled"] == "yes" || params["disabled"] == "true"),
- }
- if err := e.sdwanMgr.Save(group); err != nil {
- return ColorRed + "Error adding SD-WAN group: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("SD-WAN group %q created.\n", name) + ColorReset
- case "set":
- pos, params := parseArgs(parts[1:])
- if len(pos) == 0 && params["name"] == "" && params["id"] == "" {
- return ColorRed + "Usage: /ip sdwan set <id|name> [mode=...] [auto-nat=yes|no] [probe-interval=N] [comment=...]\n" + ColorReset
- }
- idOrName := ""
- if len(pos) > 0 {
- idOrName = pos[0]
- } else if params["name"] != "" {
- idOrName = params["name"]
- } else {
- idOrName = params["id"]
- }
- group, found := e.sdwanMgr.Get(idOrName)
- if !found {
- return ColorRed + fmt.Sprintf("SD-WAN group %q not found\n", idOrName) + ColorReset
- }
- if m, ok := params["mode"]; ok {
- group.Mode = sdwan.SDWANMode(m)
- }
- if hf, ok := params["hash-fields"]; ok {
- group.HashFields = hf
- }
- if an, ok := params["auto-nat"]; ok {
- group.AutoNAT = (an == "yes" || an == "true")
- }
- if pi, ok := params["probe-interval"]; ok {
- if v, err := strconv.Atoi(pi); err == nil && v > 0 {
- group.ProbeInterval = v
- }
- }
- if c, ok := params["comment"]; ok {
- group.Comment = c
- }
- if err := e.sdwanMgr.Save(group); err != nil {
- return ColorRed + "Error updating SD-WAN group: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("SD-WAN group %q updated.\n", group.Name) + ColorReset
- case "remove", "delete", "del":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip sdwan remove <id|name>\n" + ColorReset
- }
- idOrName := parts[1]
- if err := e.sdwanMgr.Delete(idOrName); err != nil {
- return ColorRed + "Error deleting SD-WAN group: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("SD-WAN group %q removed.\n", idOrName) + ColorReset
- case "enable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip sdwan enable <id|name>\n" + ColorReset
- }
- idOrName := parts[1]
- if err := e.sdwanMgr.SetState(idOrName, false); err != nil {
- return ColorRed + "Error enabling SD-WAN group: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("SD-WAN group %q enabled.\n", idOrName) + ColorReset
- case "disable":
- if len(parts) < 2 {
- return ColorRed + "Usage: /ip sdwan disable <id|name>\n" + ColorReset
- }
- idOrName := parts[1]
- if err := e.sdwanMgr.SetState(idOrName, true); err != nil {
- return ColorRed + "Error disabling SD-WAN group: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("SD-WAN group %q disabled.\n", idOrName) + ColorReset
- default:
- return ColorRed + "Usage: /ip sdwan [print|status|add|set|remove|enable|disable|member]\n" + ColorReset
- }
- }
- func (e *Executor) handleSDWANMember(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" || parts[0] == "show" {
- _, params := parseArgs(parts)
- groupFilter := params["group"]
- groups := e.sdwanMgr.List()
- tbl := NewTable("Flags", "Group", "Interface", "Gateway", "Weight", "Priority", "Target", "Status", "Latency", "Loss", "Jitter")
- for _, g := range groups {
- if groupFilter != "" && !strings.EqualFold(g.Name, groupFilter) && !strings.EqualFold(g.ID, groupFilter) {
- continue
- }
- for _, m := range g.Members {
- flag := " "
- if m.Disabled || g.Disabled {
- flag = "X"
- }
- statusCol := string(m.Status)
- tbl.AddRow(
- flag,
- g.Name,
- m.Interface,
- m.Gateway,
- fmt.Sprintf("%d", m.Weight),
- fmt.Sprintf("%d", m.Priority),
- m.TargetProbe,
- statusCol,
- fmt.Sprintf("%.1fms", m.LiveLatency),
- fmt.Sprintf("%.1f%%", m.PacketLoss),
- fmt.Sprintf("%.1fms", m.Jitter),
- )
- }
- }
- return tbl.String()
- }
- if user != nil && user.Role != auth.RoleFull {
- return ColorRed + "Permission denied: admin role required\n" + ColorReset
- }
- sub := strings.ToLower(parts[0])
- switch sub {
- case "add":
- _, params := parseArgs(parts[1:])
- groupName := params["group"]
- iface := params["interface"]
- if groupName == "" || iface == "" {
- return ColorRed + "Usage: /ip sdwan member add group=<group> interface=<iface> [gateway=<gw>] [weight=50] [priority=1] [target-probe=1.1.1.1] [max-latency=250] [max-loss=20] [comment=\"...\"]\n" + ColorReset
- }
- weight := 50
- if wStr, ok := params["weight"]; ok {
- if v, err := strconv.Atoi(wStr); err == nil && v > 0 {
- weight = v
- }
- }
- priority := 1
- if pStr, ok := params["priority"]; ok {
- if v, err := strconv.Atoi(pStr); err == nil && v > 0 {
- priority = v
- }
- }
- maxLat := 250
- if lStr, ok := params["max-latency"]; ok {
- if v, err := strconv.Atoi(lStr); err == nil && v > 0 {
- maxLat = v
- }
- }
- maxLoss := 20
- if mlStr, ok := params["max-loss"]; ok {
- if v, err := strconv.Atoi(mlStr); err == nil && v >= 0 {
- maxLoss = v
- }
- }
- targetProbe := params["target-probe"]
- if targetProbe == "" {
- targetProbe = "1.1.1.1"
- }
- mem := &sdwan.SDWANMember{
- Interface: iface,
- Gateway: params["gateway"],
- Weight: weight,
- Priority: priority,
- TargetProbe: targetProbe,
- MaxLatency: maxLat,
- MaxLoss: maxLoss,
- Comment: params["comment"],
- Disabled: (params["disabled"] == "yes" || params["disabled"] == "true"),
- }
- if err := e.sdwanMgr.AddMember(groupName, mem); err != nil {
- return ColorRed + "Error adding member: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Interface %s added to SD-WAN group %s.\n", iface, groupName) + ColorReset
- case "set":
- _, params := parseArgs(parts[1:])
- groupName := params["group"]
- iface := params["interface"]
- if groupName == "" || iface == "" {
- return ColorRed + "Usage: /ip sdwan member set group=<group> interface=<iface> [gateway=...] [weight=...] [priority=...] [target-probe=...]\n" + ColorReset
- }
- group, found := e.sdwanMgr.Get(groupName)
- if !found {
- return ColorRed + fmt.Sprintf("SD-WAN group %q not found\n", groupName) + ColorReset
- }
- var targetMember *sdwan.SDWANMember
- for _, m := range group.Members {
- if m.Interface == iface || m.ID == iface {
- targetMember = m
- break
- }
- }
- if targetMember == nil {
- return ColorRed + fmt.Sprintf("Member %q not found in group %s\n", iface, groupName) + ColorReset
- }
- if gw, ok := params["gateway"]; ok {
- targetMember.Gateway = gw
- }
- if wStr, ok := params["weight"]; ok {
- if v, err := strconv.Atoi(wStr); err == nil && v > 0 {
- targetMember.Weight = v
- }
- }
- if pStr, ok := params["priority"]; ok {
- if v, err := strconv.Atoi(pStr); err == nil && v > 0 {
- targetMember.Priority = v
- }
- }
- if tp, ok := params["target-probe"]; ok {
- targetMember.TargetProbe = tp
- }
- if lStr, ok := params["max-latency"]; ok {
- if v, err := strconv.Atoi(lStr); err == nil && v > 0 {
- targetMember.MaxLatency = v
- }
- }
- if mlStr, ok := params["max-loss"]; ok {
- if v, err := strconv.Atoi(mlStr); err == nil && v >= 0 {
- targetMember.MaxLoss = v
- }
- }
- if c, ok := params["comment"]; ok {
- targetMember.Comment = c
- }
- if dStr, ok := params["disabled"]; ok {
- targetMember.Disabled = (dStr == "yes" || dStr == "true")
- }
- if err := e.sdwanMgr.UpdateMember(groupName, targetMember); err != nil {
- return ColorRed + "Error updating member: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Member %s in SD-WAN group %s updated.\n", iface, groupName) + ColorReset
- case "remove", "delete", "del":
- _, params := parseArgs(parts[1:])
- groupName := params["group"]
- iface := params["interface"]
- if groupName == "" && len(parts) >= 3 {
- groupName = parts[1]
- iface = parts[2]
- }
- if groupName == "" || iface == "" {
- return ColorRed + "Usage: /ip sdwan member remove group=<group> interface=<iface>\n" + ColorReset
- }
- if err := e.sdwanMgr.DeleteMember(groupName, iface); err != nil {
- return ColorRed + "Error removing member: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Member %s removed from SD-WAN group %s.\n", iface, groupName) + ColorReset
- case "enable":
- _, params := parseArgs(parts[1:])
- groupName := params["group"]
- iface := params["interface"]
- if groupName == "" || iface == "" {
- return ColorRed + "Usage: /ip sdwan member enable group=<group> interface=<iface>\n" + ColorReset
- }
- group, found := e.sdwanMgr.Get(groupName)
- if !found {
- return ColorRed + fmt.Sprintf("SD-WAN group %q not found\n", groupName) + ColorReset
- }
- var targetMember *sdwan.SDWANMember
- for _, m := range group.Members {
- if m.Interface == iface || m.ID == iface {
- targetMember = m
- break
- }
- }
- if targetMember == nil {
- return ColorRed + fmt.Sprintf("Member %q not found in group %s\n", iface, groupName) + ColorReset
- }
- targetMember.Disabled = false
- if err := e.sdwanMgr.UpdateMember(groupName, targetMember); err != nil {
- return ColorRed + "Error enabling member: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Member %s in SD-WAN group %s enabled.\n", iface, groupName) + ColorReset
- case "disable":
- _, params := parseArgs(parts[1:])
- groupName := params["group"]
- iface := params["interface"]
- if groupName == "" || iface == "" {
- return ColorRed + "Usage: /ip sdwan member disable group=<group> interface=<iface>\n" + ColorReset
- }
- group, found := e.sdwanMgr.Get(groupName)
- if !found {
- return ColorRed + fmt.Sprintf("SD-WAN group %q not found\n", groupName) + ColorReset
- }
- var targetMember *sdwan.SDWANMember
- for _, m := range group.Members {
- if m.Interface == iface || m.ID == iface {
- targetMember = m
- break
- }
- }
- if targetMember == nil {
- return ColorRed + fmt.Sprintf("Member %q not found in group %s\n", iface, groupName) + ColorReset
- }
- targetMember.Disabled = true
- if err := e.sdwanMgr.UpdateMember(groupName, targetMember); err != nil {
- return ColorRed + "Error disabling member: " + err.Error() + "\n" + ColorReset
- }
- return ColorGreen + fmt.Sprintf("Member %s in SD-WAN group %s disabled.\n", iface, groupName) + ColorReset
- default:
- return ColorRed + "Usage: /ip sdwan member [print|add|set|remove|enable|disable]\n" + ColorReset
- }
- }
- // Helpers for sorting/filtering
- func sortStrings(s []string) {
- sort.Strings(s)
- }
|