| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704 |
- 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-template", "interface-templates", "template", "templates":
- return e.handleBabelInterfaceTemplate(parts[1:], user)
- case "interface", "interfaces":
- return e.handleBabelInterface(parts[1:], user)
- case "interface-live", "interfaces-live", "live":
- return e.handleBabelInterfacesLive(parts[1:], user)
- case "neighbor", "neighbors":
- nbrs := e.dynRoutingMgr.GetBabelNeighbors()
- tbl := NewTable("IP-ADDRESS", "INTERFACE", "METRIC", "COST", "ROUTES", "HELLOS", "EXPIRES", "AUTH", "RTT")
- for _, n := range nbrs {
- rttStr := ""
- if n.RTT > 0 {
- rttStr = fmt.Sprintf("%.2fms", n.RTT)
- }
- tbl.AddRow(n.IP, n.Interface, fmt.Sprintf("%d", n.Metric), fmt.Sprintf("%d", n.Cost), fmt.Sprintf("%d", n.Routes), fmt.Sprintf("%d", n.Hellos), n.Expires, n.Auth, rttStr)
- }
- return tbl.String()
- case "routes", "route":
- routes := e.dynRoutingMgr.GetBabelRoutes()
- tbl := NewTable("PREFIX", "NEXT-HOP", "INTERFACE", "METRIC", "FLAG", "SEQNO", "EXPIRES")
- for _, r := range routes {
- tbl.AddRow(r.Prefix, r.NextHop, r.Interface, fmt.Sprintf("%d", r.Metric), r.Flag, fmt.Sprintf("%d", r.Seqno), r.Expires)
- }
- return tbl.String()
- default:
- return ColorRed + "Usage: /routing babel [instance|interface|interface-template|interface-live|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("#", "FLAGS", "NAME", "ROUTER-ID", "REDISTRIBUTE", "METRIC", "STATUS")
- for idx, inst := range instances {
- st := "enabled"
- flags := ""
- if inst.Running {
- flags = "R"
- }
- if inst.Disabled {
- st = "disabled"
- flags = "X"
- }
- 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), flags, inst.Name, rID, redistStr, fmt.Sprintf("%d", inst.DefaultMetric), 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) handleBabelInterfacesLive(parts []string, user *auth.PublicUser) string {
- ifaces := e.dynRoutingMgr.GetBabelInterfacesLive()
- tbl := NewTable("#", "FLAGS", "INTERFACE", "INSTANCE", "STATE", "AUTH", "RXCOST", "NBRS", "TIMER", "NEXT-HOP-V4", "NEXT-HOP-V6")
- for idx, iface := range ifaces {
- tbl.AddRow(fmt.Sprintf("%d", idx), "D", iface.Interface, iface.Instance, iface.State, iface.Auth, fmt.Sprintf("%d", iface.RxCost), fmt.Sprintf("%d", iface.Neighbors), iface.Timer, iface.NextHopV4, iface.NextHopV6)
- }
- return tbl.String()
- }
- func (e *Executor) handleBabelInterface(parts []string, user *auth.PublicUser) string {
- if len(parts) == 0 || parts[0] == "print" || parts[0] == "pr" {
- isTemplate := false
- for _, p := range parts {
- if strings.EqualFold(p, "template") || strings.EqualFold(p, "templates") {
- isTemplate = true
- break
- }
- }
- liveIfaces := e.dynRoutingMgr.GetBabelInterfacesLive()
- if !isTemplate && len(liveIfaces) > 0 {
- tbl := NewTable("#", "FLAGS", "INTERFACE", "INSTANCE", "STATE", "AUTH", "RXCOST", "NBRS", "TIMER", "NEXT-HOP-V4", "NEXT-HOP-V6")
- for idx, iface := range liveIfaces {
- tbl.AddRow(fmt.Sprintf("%d", idx), "D", iface.Interface, iface.Instance, iface.State, iface.Auth, fmt.Sprintf("%d", iface.RxCost), fmt.Sprintf("%d", iface.Neighbors), iface.Timer, iface.NextHopV4, iface.NextHopV6)
- }
- return tbl.String()
- }
- return e.handleBabelInterfaceTemplate([]string{"print"}, user)
- }
- return e.handleBabelInterfaceTemplate(parts, user)
- }
- func (e *Executor) handleBabelInterfaceTemplate(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 template %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 template %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 template %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 template %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)
- }
|