Sooshell

پوسته کاربری sooshell با بهره گیری از قابلیت های zsh امکانات جدیدی را در اختیار کاربران قرار میدهد. در sooshell شما در آن واحد هم به دستورات پیکربندی روتر سودار دسترسی دارید، و هم از ساختار های نحوی شل های POSIX میتوانید استفاده کنید. sooshell چه در زمینه کاربرد تعاملی و چه در زمینه script نویسی قابلیت های جدیدی را به کاربران سودار و مدیران شبکه میدهد که از طریق آن میتوانید کارایی خود را چند برابر کنید.

sooshell ویژگی های منحصر به فردی برای استفاده تعاملی کاربران ارائه کرده که کار کردن با CLI را آسان تر، کارآمد تر و لذت بخش تر میکند.

نمایش نام در Prompt

پرامت sooshell وضعیت دقیق نشست کاربری را از لحاظ مسیری پیکربندی که در آن هستیم مشخص میکند:

router1/c/interface/ge0#

به عنوان نمونه پرامت بالا نشان میدهید که ما وارد مسیر enable -> config -> interface : ge0 شده ایم.

router1/c/i/g/link-params#

یا در اینجا وارد enable -> config -> interface : ge0 -> link-params شده ایم.
این ویژگی باعث می شود که admin همیشه بداند که در حال تنظیم کردن کدام بخش هست . اگر در حال تنظیم اینترفیس هست نام اینترفیس در prompt وجود دارد یا اگر دارد acl تنظیم می کند نام acl در prompt مشخص است و باعث می شود که خطای admin کاهش یابد و مثلا اینترفیسی را اشتباهی shutdown نکند یا رولی را در acl ی حذف و اضافه نکند . این به اصطلاح context در همه مسیرهای کانفیگ وجود دارد . در class-map,policy-map,route-map,access-list,interface,ike-config,ipsec-config,… .

نکته

وقتی prompt طولانی باشد تنها حرف اول هر بخش در prompt نوشته می شود در مثال بالا c/i/g که به ترتیب نشانگر config,interface و ge0 می باشد . هر جا که نیاز داشتید که prompt را به شکل کامل ببینید می توانید از دستور pwd استفاده کنید :

n1/c/i/g/link-params# pwd          
/enable/config/interface/ge0/link-params/
n1/c/i/g/link-params# 

تکمیل خودکار

با استفاده از قابلیت تکمیل خودکار میتوان از نوشتن کامل دستورات خود داری و در زمان صرفه جویی کرد. برای کامل کردن یک کلمه کافیست کلید TAB را فشار دهید تا sooshell کلمات ممکن برای تکمیل دستورات را به شما پیشنهاد دهد:

router1/config# p<TAB>

password    pbr     pbr-map     pseudowire

میتوان با فشردن کلیدهای جهت دار ⬆️➡️⬅️⬇️ یا همان کلید TAB یکی از گزینه ها را انتخاب نمود. همچنین اگر sooshell فقط یک حالت ممکن برای تکمیل کردن دستور پیدا کند، بدون نیاز به فشردن مجدد کلید دیگری آن را بطور خودکار کامل میکند.

router1/config# ps<TAB>
router1/config# pseudowire

sooshell-tab

تاریخچه

sooshell لیستی از دستوراتی که قبلا وارد کرده اید را ذخیره میکند. تاریخچه دستورات با بستن سشن از بین نمیرد و در اجرای بعدی sooshell نیز در دسترس خواهند بود. با فشردن کلید های جهت دار بالا و پایین ⬆️⬇️ میتوان تاریخچه را مشاهده کرد و دستوراتی که قبلا وارد شده اند را مجددا اجرا کنید.

هایلایت

sooshell با رنگی کردن کلمه ابتدایی دستورات نشان میدهد که آیا دستور وارد شده صحیح است یا خیر:

  • رنگ قرمز: چنین دستوری وجود ندارد

  • رنگ سبز: دستور وجود دارد

  • رنگ زرد: دستور تا اینجایی که تایپ شده مبهم است و لازم است مقدار بیشتری از آن تایپ شود

  • رنگ آبی: دستور وجود دارد، اما در مسیر پیکربندی قبلی بوده و اجرای آن باعث میشود تا از مسیر فعلی خارج شویم.

    sooshell-cmd-color

پیشنهاد خودکار

sooshell همزمان با تایپ کردن کاربر بر اساس تاریخچه دستوراتی که کاربر وارد کرده و ورودی که تا آن لحظه تایپ کرده، یک دستور را به صورت کمرنگ در جلوی ورودی کاربر پیشنهاد میدهد. کاربر میتواند با فشردن کلید جهت دار سمت راست ➡️ آن پیشنهاد را انتخاب کند و یا در غیر این صورت به تایپ کردن خود ادامه دهد.

sooshell-suggest

script نویسی

قابلیت script نویسی sooshell موجب میشود تا بتوانیم بسیاری از کارهای دستی خسته کننده را به صورت اتوماتیک انجام دهیم. سینتکس script های sooshell همان سینتکس شل های POSIX در سیستم عامل های یونیکسی نظیر Bash و Zsh میباشد. قابل برنامه نویسی بودن sooshell این امکان را به مدیران شبکه میدهد تا برای اتوماسیون پیکربندی و هر گونه روال دلخواهی بتوانند script مورد نیاز خود را بنویسند و اجرا کنند.

script ها را به شکل تعاملی نیز میتوان اجرا کرد. کافی است کد اسکریپتی که از قبل نوشته اید را در ترمینال paste کنید تا اجرا شود. اما روش کارآمد تر اجرای script ها از طریق ذخیره کردن و فراخوانی آنها با نام آنها میباشد که در ادامه به آن میپردازیم.

ذخیره و فراخوانی اسکریپت

TODO

در اینجا با مثال های متعدد نشان خواهیم داد که چگونه حداکثر استفاده را از قابلیتهای script نویسی sooshell ببریم:

تعریف متغیر

تعریف متغیر ها در متن دستوراتی که اجرا میکنم، این امکان را به ما میدهد تا بتوانیم صرفا با تغییر دادن مقدار آن متغیر، دستورات خود را به ازای مقادیر مختلفی اجرا کنیم، بدون آنکه نیاز باشد خود دستور را تغییر دهیم.

# set variable IFNAME to ge0
IFNAME=ge0
# following command evaluates to 'interface ge0'
interface $IFNAME

پاک کردن متغیر:

# delete IFNAME
unset IFNAME

لازم به ذکر است که خط هایی که با ‍‍# شروع میشوند کامنت هستند.

سینتکس اجرای دستورات

میتوان دستورات فوق را در یک خط نوشت:

IFNAME=ge0; interface $IFNAME && { no shut; quit; }
  • دستورات توسط کاراکتر ; از یکدیگر جدا شده اند.

  • سری && نیز مانند ; عمل میکند، با این تفاوت که دستور دوم تنها در صورتی اجرا میشود که دستور اول با موفقیت اجرا شده باشد.

  • کاراکتر های {} یک یا چند دستور را در یک بلوک قرار میدهند.

در مثال بالا اگر مقداری که در متغیر IFNAME قرار داده باشیم نام یک اینترفیس موجود نباشد، دستور interface $IFNAME شکست میخورد و دستورات قرار داده شده در بلوک اجرا نمیشوند.

پردازش خروجی

دستور زیر را در نظر بگیرید:

router1# show interface brief
Interface       Status  VRF             Addresses
---------       ------  ---             ---------
lo              up      default         
ge0             up      default         192.168.1.1/24
ge1             up      default         192.168.2.1/24
wg0             down    default         10.10.49.1/16

میخواهیم فقط اطلاعات مربوط به ge1را دریافت کنیم . sooshell این امکان را به ما میدهد تا خروجی یک دستور را به عنوان ورودی دستور دیگری قرار دهیم. دستور grep میتواند در ورودی خود به دنبال یک الگو بگردد. کافی است نام اینترفیس مورد نظرمان را همراه با ورودی دستور قبلی به grep بدهیم:

router1# show interface brief | grep ge1
ge1             up      default         192.168.2.1/24

دستور دیگری که میتوان از آن استفاده کرد دستور awk است. این دستور قابلیت جست و جوی پیشرفته تری را به کاربر ارائه میدهد. مثلا میخواهیم آدرس آی پی اینترفیس ge1 را پیدا کنیم:

router1# show interface brief | awk 'NR > 1 && $1 == "ge1" { print $4 }'
192.168.2.1/24

این احتمال نیز وجود دارد که بخواهیم خروجی یک دستور را در یک متغیر ذخیره کنیم تا بعدا از آن استفاده کنیم که این کار با با کمک سینتکس VAR=$(COMMAND) انجام میدهیم:

ip_ge1=$(show interface brief | awk '$1 == "ge1" { print $4 }')

عملیات شرطی

گاهی نیاز داریم برخی دستورات فقط در شرایط خاصی اجرا شوند. در sooshell هر شرط خود در واقع یک دستور است که موفقیت آمیز بودن اجرای دستور به منزله برقرار بودن شرط میباشد. فرض کنید میخواهیم همه اینترفیس ها را در صورتی که تعدادشان از ۵ تا کمتر باشد نشان دهیم:

# get all rows except the first 2 heading rows
interfaces=$(show interface brief | tail +3)
# count the number of rows
count=$(wc -l <<< $interfaces)
# check if we have less than 5 rows
if [[ $count -lt 5 ]]
then
    # print all rows
    echo $interfaces
fi
  • در خظ اول با استفاده از دستور tail فقط از خط سوم به بعد را ذخیره میکنیم.

  • تعداد ردیف ها با استفاده از دستور wc -l بدست می آوریم.

  • در خط بعدی از ساختار if استفاده میکنیم تا شرط ذکر شده را بررسی کنیم

  • اگر شرط برقرار بود همه ردیف ها را چاپ میکنیم

با سینتکس command arg1 arg2 <<< $variable میتوان مقدار یک متغییر را به عنوان ورودی به یک دستور داد.

سینتکس کلی ساختار کنترلی شرطی به این صورت است:

if command1
then
    command2
    command3
fi

که در صورت موفقیت آمیز بودن دستور اول، هر دو دستور بعدی اجرا میشوند. در مثالی که ذکر کردیم دستور اول همان [[ $count -lt 5 ]] میباشد. رشته [[ در واقع یک دستور است که با آرگومان های $count,-lt,5 فراخوانی شده. آرگومان دوم آن یعنی -lt به معنی کوچکتر میباشد و دستور در صورتی که آرگومان اول از سومی کوچکتر باشد موفقیت آمیز بوده و شرط برقرار میشود.

اکنون فرض کنید بخواهیم اگر تعداد اینترفیس ها بین ۵ الی ۱۰ دستور بود فقط اسم اینترفیس ها و اگر بیشتر بود فقط تعداد آنها چاپ شود:

# get all rows except the first 2 heading rows
interfaces=$(show interface brief | tail +3)
# count the number of rows
count=$(wc -l <<< $interfaces);
# check if we have less than 5 rows
if [[ $count -lt 5 ]]
then
    # print all rows
    echo $interfaces
elif [[ $count -lt 10 ]]
    # print first column of all rows
    echo $interfaces | awk '{print $1}'
else
    echo "number of interfaces: $count"
fi

سینتکس کامل ساختار کنترلی شرطی به این صورت است:

if cond1
then
    command1-1
    command1-2
    ...
elif cond2
then
    command2-1
    command2-2
    ...
elif...
fi

دیگر عملگرهای شرطی:

  • آیا طول رشته a بیشتر از صفر است؟ [[ -n a ]]

  • آیا طول رشته a برابر با صفر است؟ [[ -z a ]]

  • آیا رشته های a و b با یکدیگر برابر اند؟ [[ a == b ]]

  • آیا رشته های a و b با یکدیگر برابر نیستند؟ [[ a != b ]]

  • آیا رشته a با رگکس b مچ میشود؟ [[ a =~ b ]]

  • آیا عدد a کوچکتر از عدد b است؟ [[ a -lt b ]]

  • آیا عدد a بزرگتر از عدد b است؟ [[ a -gt b ]]

  • آیا عدد a کوچکتر-مساوی عدد b است؟ [[ a -le b ]]

  • آیا عدد a بزرگتر-مساوی عدد b است؟ [[ a -ge b ]]

  • آیا عدد a مساوی با عدد b است؟ [[ a -eq b ]]

  • آیا عدد a نامساوی با عدد b است؟ [[ a -ne b ]]

دستورات کمکی

هنگامی که در حال تعامل با sooshell یا اجرای script هستید، همواره در یک مسیر پیکربندی قرار دارید.

router1/c/interface/ge0# node
interface

با این دستور میتوانید نام نودی که در آن هستید را بدست بیاورید. اگر آن نود اطلاعات اضافه ای داشته باشد نیز میتوان آن را بدست آورد:

router1/c/interface/ge0# node -x
ge0

ضمنا میتوان از دستور شرطی زیر نیز برای بررسی اینکه در نود خاصی هستیم یا خیر استفاده کنیم:

# check if we're in node
[[ -in config ]]

مثال:

if [[ ! -in config ]]
then
    echo can\'t run from $(node)
    echo must be in config
    return 1
fi
# or simply
[[ ! -in config ]] && return 1

حلقه for

در script sooshell برای انجام کارهای تکراری میتوان از حلقه تکرار استفاده نمود. حلقه for در sooshell سینتکس ساده ای دارد و با استفاده از آن میتوان به ازای بازه ای از اعداد یک کار را انجام داد. مثلا در اینجا میخواهیم دستور مربوط به اضافه کردن نت استاتیکی به ازای اعداد ۱۲۰۰ تا ۱۴۰۰ تکرار شود ( به ازای خود ۱۲۰۰ و ۱۴۰۰ هم اجرا میشود ):

for i in {1200..1400}; do
    ip nat inside static source tcp "1.1.1.10" $i "200.1.2.2" $i
done

در این حلقه متغیر i همان اندیس حلقه است که در دستورات درون حلقه میتوان از آن استفاده کرد.

گاهی میخواهیم به ازای هر عنصر در یک آرایه دستوراتی اجرا شوند. در آن صورت مانند مثال زیر عمل میکنیم:

# Define variables for VLAN configuration
vlan_ids=(100 200 300)
vlan_ifname="ge0"

# Loop through VLAN configuration
for idx in $vlan_ids; do
    vlan_name="$vlan_ifname.$idx"
    echo "Configuring VLAN $vlan_name ..."
    interface "$vlan_name"
    encapsulation dot1q $idx
    bridge-group $idx
    no shutdown
    exit
    # tag rewrite in access side
    ifidx=$((idx / 100))
    interface ge$ifidx
    no ip address
    rewrite tag push 1 dot1q $idx
    bridge-group $idx
done

echo "VLAN configuration completed."

برای مثال با استفاده از script بالا می توان تنظیم vlan را در روتر های n1,n2 انجام داد . در این مثال اینترفیس ge0 سمت Trunk و اینترفیس های ge1,ge2و ge3 به ترتیب مربوط به vlan های 100 و200 و 300 هستند :

sooshel-vlan

n1/config# vlan_ids=(100 200 300)
n1/config# vlan_ifname="ge0"
n1/config# 
n1/config# # Loop through VLAN configuration
n1/config# for idx in $vlan_ids; do
for>     vlan_name="$vlan_ifname.$idx"
for>     echo "Configuring VLAN $vlan_name ..."
for>     interface "$vlan_name"
for>     encapsulation dot1q $idx
for>     bridge-group $idx
for>     no shutdown
for>     exit
for>     # tag rewrite in access side
for>     ifidx=$((idx / 100))
for>     interface ge$ifidx
for>     no ip address
for>     rewrite tag push 1 dot1q $idx
for>     bridge-group $idx
for> done
Configuring VLAN ge0.100 ...
Configuring VLAN ge0.200 ...
Configuring VLAN ge0.300 ...
n1/c/interface/ge3# 

همین script را هم می توان در n2 اجرا کرد و ارتباط بین vlan ها را برقرار کرد .

در این مثال دستورات درون حلقه ۳ بار اجرا میشوند، که یکبار idx برابر با 100، یکبار 200 و یکبار هم 300 میباشد.

مثال حذف تمامی sub interface ها

برای حذف sub interface ها ابتدا باید لیست آنها را بدست آورده و سپس shutdown کنیم و در آخر آن ها را حذف می کنیم

subifs=($(do show int brief | grep -E ".+\..+" | awk '{print $1}'))

for if in $subifs; do
  interface $if
    shutdown
    q
  no int $if  
done

حلقه while

نوعی دیگر از حلقه تکرار حلقه های while هستند که تا زمانی که شرط آنها برقرار باشد دستورات درون آنها اجرا میشوند. در مثال زیر اسکریپتی نوشته شده که نام یک پروتکل را از ورودی میخواند و تا مادامی که ورودی خالی نباشد، آن پروتکا را deny میکند و پروتکل بعدی را از ورودی میگیرد:

# read protocol names (one per line) and deny them in current ACL
[[ -in ip-access-list ]] || {
    echo Error: Must be in ACL node 1>&2
    return 1
}
while read PROTOCOL && [[ -n $PROTOCOL ]]
do
    deny $PROTOCOL any any
    echo denied protocol $PROTOCOL in ACL $(node -x)
done
permit any

دستور read یک خط از ورودی میخواند و آن را در متغیری که نامش را به عنوان آرگومان دریافت کرده (در اینجا Protocol) قرار میدهد. اگر ورودی خالی دریافت کند، دستور شکست میخورد و شرط حلقه برقرار نمیشود.

جست و جو در کانفیگ

گاهی اوقات پیش می آید که که برای یک ip خاص یا یک سری از ip ها تنظیماتی در ACL ها و NAT و بخش های دیگر کانفیگ انجام داده ایم و این تنظیمات بزرگ و قدیمی است و دقیقا یادمان نیست چطور کانفیگ کرده ایم . حال اگر بخواهیم مثلا کلیه تنظیماتی که برای 192.168.1.24 انجام داده ایم برای 192.168.1.33 هم انجام دهیم کار کمی سخت خواهد بود .

با استفاده از ویژگی جست و جوی پیشرفته در کانفیگ می توانیم جاهایی که تنظیمی برای 192.168.1.24 انجام شده را فیلتر کنیم و دقیقا همان تنظیم برای 192.168.1.33 انجام دهیم . در این فیلتر کردن کانفیگ می توانیم مشخص چند خط بعد و قبل از عبارت مورد نظر را هم برای ما نشان دهد . همچنین می توانیم عبارت مورد نظر را با regex مشخص کنیم :

sh running-config | grep 192.168.1.24 -B 10 -A 5

sooshell-filter-config

جست و جو در لاگ ها

زمانی که حجم لاگ ها زیاد باشد بررسی log ها و پیدا کردن مشکل مورد نظر سخت خواهد بود شما با استفاده از modifier ها می توانید بخش های خاصی از لاگ ها را بررسی کند و به جای بررسی خط به خط لاگ ها که هم زمانبر خواهد بود و هم خسته کننده ، شما می تواند=ید با استفاده از regex ها الگوی مورد نظر را در لاگ ها جست و جو کنید . برای مثال اگر بخواهیم لاگ مربوط به قطع و وصل شدن تونل های وایرگارد را مشاهده کنیم کافی است لاگ ها را به شکل زیر با استفاده از regex فیلتر کنیم :

sh log all | grep -i -E "wireguard.+peer.+ (connected|disconnected)"
Jun 27 21:14:21 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jun 28 10:34:18 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jun 28 21:26:48 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jun 29 07:37:11 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jun 29 17:18:26 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jun 30 08:09:43 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jun 30 17:56:43 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jul 01 08:14:20 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jul 01 20:22:34 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jul 02 08:10:33 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jul 02 20:52:22 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jul 03 06:31:55 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected
Jul 03 20:05:22 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 disconnected
Jul 04 06:38:25 Soodar-router zebra[675]: Wireguard peer MSHD@wireguard403 connected

5 خط قبل و 3 خط بعد از این لاگ ها هم چاپ شود تا لاگ های قبل و بعد از این الگو را هم داشته باشیم : به شکل زیر میتوان تعیین کرد که

sh log all | grep -i -E "wireguard.+peer.+ (connected|disconnected)" -B 5 -A 3

sooshell-log

یا در مثال زیر لاگهای اتصالات موفق و ناموفق ssh به روتر را بدین شکل از بین لاگ ها استخراج می کنیم :

sooshell-log2

در مثالی دیگر up/down شدن اینترفیس ها را در لاگها فیلتر می کنیم :

sooshell-log3

اگر باز هم میزان خروجی لاگ ها بعد از فیلتر زیاد باشد می توانیم مشخص کنیم چند خط پایانی یا ابتدایی را برای ما نشان دهد :

sooshell-log4

توابع

sooshell امکان تعریف تابع و فراخوانی آنها را جهت اسفاده مجدد از کدها به کاربر میدهد. توابع هنگام فراخوانی میتوانند آرگومان دریافت کنند و حتی یک کد وضعیت نیز بازگردانند. کد وضعیت تابع مانند کد وضعیت بقیه دستورات عمل میکند، کد صفر به معنای موفقیت و سایر کدها به معنای شکست میباشد. توابع همچنین میتوانند مانند دستورات پردازش خروجی عمل کنند، یعنی بعد از کاراکتر | فراخوانی شوند تا خروجی دستور قبلی را به عنوان ورودی بگیرند.

در اینجا یک تابع ساده تعریف میکنیم که اگر همه آرگومان های آن با هم برابر نبودند، شکست میخورد:

check-equality()
{
    if [[ $# -eq 0 ]]; then
        return 2
    fi
    for arg in $@; do
        if [[ $arg -neq $1 ]]; then
            return 1
        fi
    done
    return 0
}

برای تعریف تابع باید از سینتکس name(){ ... } استفاده کنیم. تعریف تابع در بین دو آکولاد قرار میگیرد. در ابتدای تابع بررسی میکنیم آیا اصلا آرگومانی به تابع داده شده یا خیر. عبارت $# بیانگر تعداد آرگومانها میباشد. میخواهیم اگر آرگومانی داده نشد تابع شکست بخورد و مقدار ۲ باز گردانده شود. در ادامه یک حلقه میگذاریم که تک تک آرگومان ها را با استفاده از عبارت $@ پیمایش میکند و آنها را با اولین آرگومان یعنی $1 مقایسه میکند (بطور کلی $n یعنی آرگومان n ام.) و اگر برابر نبودند مقدار ۱ را به معنی خطا بازمیگرداند.

اکنون میخواهیم تابعی که نوشته ایم را فراخوانی کنیم. فراخوانی تابع به صورت check-equality arg1 arg2 arg3 ... میباشد. در مثال پایین در خط اول با عملگر && بررسی میکنیم که آیا اجرا موفقیت آمیز بوده یا خیر، چرا که اگر شکست خورده باشد دستور بعدی نباید اجرا شود. در خط بعدی نیز از شکست خوردن تابع اطمینان حاصل میکنیم، زیرا عملگر || با معنی “یا” باعث میشود تا اگر دستور اول موفقیت آمیز بود، sooshell از اجرای دستور بعدی صرف نظر کند.

check-equality a a a && echo '1st' case passed
check-equality a a b || echo '2nd' case failed

خروجی اجرا:

1st case passed
2nd case passed

تابعی برای دریافت IP ,MAC اینترفیسها

show_int(){
sh int json | jq -r    ' ["Interface           ", "IP Address        ", "MAC Address       "] ,
(to_entries[] | [  .key,  (.value.ipAddresses | if length > 0 then .[0].address else "N/A" end),  .value.hardwareAddress] | map(if length < 20 then . + " " * (20 - length) else . end)) | @tsv'
}

sooshell-func

تابعی برای up کردن همه اینترفیس های vlan

vlanup()
{
    if [[ ! -in config ]]; then
        echo this command must be run in config node, not $(node) node 1>&2
        return 1
    fi

    for ifname in $(interfaces); do
        if [[ $ifname =~ .*\\..* ]]; then
            interface $ifname
            no shut
            quit
        fi
    done 
}
# usage
valnup

تنظیم IP در اینترفیس ها

تابعی که جفتهای اینترفیس/آیپی را دریافت میکند و آیپی ها را روی اینترفیس تنظیم میکند.

addips()
{
    local retcode=0

    if [[ ! -in config ]]; then
        echo this command must be run in config node, not $(node) node 1>&2
        return 1
    fi

    for pair in "$@"
    do
        ifname=($(cut -d '=' -f '1' <<< $pair))
        ipaddr=($(cut -d '=' -f '2' <<< $pair))
        if do sh int json | jq -e -r ".$ifname" &> /dev/null
        then
            echo "On $ifname set IP $ipaddr"
            interface $ifname
            ip address $ipaddr
            no shut
            quit
        else
            echo "interface $ifname does not exist" 1>&2
            retcode=1
        fi
    done
    return $retcode
}
# usage
addips wg0="192.168.1.1/24" wg1="192.168.2.1/24" wg2="192.168.3.1/24"

تابعی برای ساختن تونل IPSec

تابعی که چهار ورودی از کاربر می گیرد و تمامی تنظیمات تونل IPSec را که بسیار زمانبر است انجام میدهد : شما لازم است source_ip , destination_ip , local_node_id , remote_node_id . را به عنوان ورودی به تابع بدهید node_id ها می تواند عددی باشد که شما به هر روتر در سناریوی خود اختصاص می دهید . در ادامه با ذکر مثال نحوه تعریف و استفاده از این تابع را با یک کثال توضیح میدهیم .

add-ipsec()
{
if [ "$#" -ne 4 ]; then
    echo "need argumenst : $0 <source_ip> <destination_ip> <my_id> <peer_id>"
    return 1
fi

local_id="222.$3.$3.$3"
remote_id="222.$4.$4.$4"
tunnel_id=$(($3+$4))
tunnel_ip="10.0.$tunnel_id.$3/24"
local_ip=$1
remote_ip=$2

echo local_ip=$local_ip
echo remote_ip=$remote_ip
echo local_id=$local_id
echo remote_id=$remote_id
echo tunnel_id=$tunnel_id
echo tunnel_ip=$tunnel_ip
psk="salam!-$tunnel_id!"

crypto ikev2 proposal IKE_PROPOSAL
 integrity sha-96
 encryption aes-256
 group 20
!
crypto ikev2 profile IKE_PROFILE_$3_$4
 keyring local IKE_KEYRING
 identity local address $local_id
 match identity remote address $remote_id
 authentication local pre-share
 authentication remote pre-share
 proposal IKE_PROPOSAL
!
crypto ikev2 keyring IKE_KEYRING
 peer $remote_ip
  address $remote_ip
  pre-shared-key $psk
  identity address $remote_id
!
crypto ipsec transform-set IPSec_TS esp hmac sha-256 cipher aes-256
 mode transport
!
crypto ipsec profile IPSec_PROFILE_$3_$4
 set transform-set IPSec_TS
 set ikev2-profile IKE_PROFILE_$3_$4
 set security-association lifetime seconds 3600
!
!
interface tunnel$tunnel_id
 tunnel source $local_ip
 tunnel destination $remote_ip
 tunnel protection ipsec profile IPSec_PROFILE_$3_$4
 no shutdown
 ip address $tunnel_ip
!

}

ابتدا تابع را در روتر ۱ به شکل زیر تعریف می کنیم و در واقع تابع را کپی کرده و در ترمینال paste می کنیم :‍

n1/config# add-ipsec()
function> {
function> if [ "$#" -ne 4 ]; then
function then>     echo "need argumenst : $0 <source_ip> <destination_ip> <my_id> <peer_id>"
function then>     return 1
function then> fi
function> 
function> local_id="222.$3.$3.$3"
function> remote_id="222.$4.$4.$4"
function> tunnel_id=$(($3+$4))
function> tunnel_ip="10.0.$tunnel_id.$3/24"
function> local_ip=$1
function> remote_ip=$2
function> 
function> echo local_ip=$local_ip
function> echo remote_ip=$remote_ip
function> echo local_id=$local_id
function> echo remote_id=$remote_id
function> echo tunnel_id=$tunnel_id
function> echo tunnel_ip=$tunnel_ip
function> psk="salam!-$tunnel_id!"
function> 
function> crypto ikev2 proposal IKE_PROPOSAL
function>  integrity sha-96
function>  encryption aes-256
function>  group 20
function> !
function> crypto ikev2 profile IKE_PROFILE_$3_$4
function>  keyring local IKE_KEYRING
function>  identity local address $local_id
function>  match identity remote address $remote_id
function>  authentication local pre-share
function>  authentication remote pre-share
function>  proposal IKE_PROPOSAL
function> !
function> crypto ikev2 keyring IKE_KEYRING
function>  peer $remote_ip
function>   address $remote_ip
function>   pre-shared-key $psk
function>   identity address $remote_id
function> !
function> crypto ipsec transform-set IPSec_TS esp hmac sha-256 cipher aes-256
function>  mode transport
function> !
function> crypto ipsec profile IPSec_PROFILE_$3_$4
function>  set transform-set IPSec_TS
function>  set ikev2-profile IKE_PROFILE_$3_$4
function>  set security-association lifetime seconds 3600
function> !
function> !
function> interface tunnel$tunnel_id
function>  tunnel source $local_ip
function>  tunnel destination $remote_ip
function>  tunnel protection ipsec profile IPSec_PROFILE_$3_$4
function>  no shutdown
function>  ip address $tunnel_ip
function> !
function> 
function> }
n1/config# 
n1/config# 

و سپس به شکل زیر تابع را با ورودی های مورد نظر فراخوانی می کنیم .

source_ip: 200.1.5.1
destination_ip: 200.2.5.2
local_id : 1
remote_id : 2
بدین شکل تونل IPSec از روتر 1 به روتر 2 تنظیم می شود

n1/config# 
n1/config# add-ipsec 200.1.5.1 200.2.5.2  1  2
local_ip=200.1.5.1
remote_ip=200.2.5.2
local_id=222.1.1.1
remote_id=222.2.2.2
tunnel_id=3
tunnel_ip=10.0.3.1/24
n1/c/interface/tunnel3# 

تابعی برای ساخت تونل wireguard

حال فرض کنید یک سناریو به شکل زیر داریم و قصد داریم بین نود های 1 و 2 و 3 و4 تونل های ipsec به صورت fullmesh ایجاد کنیم :

ipsec-fullmesh

تونل به روتر 2 که تنظیم شد ،می توان به راحتی هر چه تمام تر تونل به روتر های ۳ و ۴ را هم ساخت :

n1/config# add-ipsec 200.1.5.1 200.3.5.3  1  3
n1/config# add-ipsec 200.1.5.1 200.4.5.4  1  4

برای تنظیم تونل در بقیه روتر ها هم کافی است این تابع را در آن ها تعریف کرده و از آن استفاده کنیم :

n2/config# add-ipsec 200.2.5.2 200.1.5.1  2  1
n2/config# add-ipsec 200.2.5.2 200.3.5.3  2  3
n2/config# add-ipsec 200.2.5.2 200.4.5.4  2  4
n3/config# add-ipsec 200.3.5.3 200.1.5.1  3  1
n3/config# add-ipsec 200.3.5.3 200.2.5.2  3  2
n3/config# add-ipsec 200.3.5.3 200.4.5.4  3  4
n4/config# add-ipsec 200.4.5.4 200.1.5.1  4  1
n4/config# add-ipsec 200.4.5.4 200.3.5.3  4  3
n4/config# add-ipsec 200.4.5.4 200.2.5.2  4  2

بدین ترتیب با استفاده از این تابع توانستیم در هر نود ۳ تونل IPSec و در مجموع ۱۲ تونل را فقط با ۱۲ خط دستور بسازیم .

برای نمونه تنظیمات به شکل زیر در n1 انجام شده است :

hostname n1
service password-encryption
no zebra nexthop kernel enable
security passwords min-length 8
log syslog errors
log monitor
no banner motd
no ntp
!
ip route 0.0.0.0/0 200.1.5.5
!
crypto ikev2 proposal IKE_PROPOSAL
 integrity sha-96
 encryption aes-256
 group 20
exit
!
crypto ikev2 profile IKE_PROFILE_1_2
 keyring local IKE_KEYRING
 proposal IKE_PROPOSAL
 match identity remote address 222.2.2.2
 identity local address 222.1.1.1
 authentication local pre-share
 authentication remote pre-share
exit
!
crypto ikev2 profile IKE_PROFILE_1_3
 keyring local IKE_KEYRING
 proposal IKE_PROPOSAL
 match identity remote address 222.3.3.3
 identity local address 222.1.1.1
 authentication local pre-share
 authentication remote pre-share
exit
!
crypto ikev2 profile IKE_PROFILE_1_4
 keyring local IKE_KEYRING
 proposal IKE_PROPOSAL
 match identity remote address 222.4.4.4
 identity local address 222.1.1.1
 authentication local pre-share
 authentication remote pre-share
exit
!
crypto ikev2 keyring IKE_KEYRING
 peer 200.2.5.2
  address 200.2.5.2
  pre-shared-key salam!-3!
  identity address 222.2.2.2
 peer 200.3.5.3
  address 200.3.5.3
  pre-shared-key salam!-4!
  identity address 222.3.3.3
 peer 200.4.5.4
  address 200.4.5.4
  pre-shared-key salam!-5!
  identity address 222.4.4.4
exit
!
crypto ipsec transform-set IPSec_TS esp hmac sha-256 cipher aes-256
 mode transport
exit
!
crypto ipsec profile IPSec_PROFILE_1_2
 set transform-set IPSec_TS
 set ikev2-profile IKE_PROFILE_1_2
 set security-association lifetime seconds 3600
exit
!
crypto ipsec profile IPSec_PROFILE_1_3
 set transform-set IPSec_TS
 set ikev2-profile IKE_PROFILE_1_3
 set security-association lifetime seconds 3600
exit
!
crypto ipsec profile IPSec_PROFILE_1_4
 set transform-set IPSec_TS
 set ikev2-profile IKE_PROFILE_1_4
 set security-association lifetime seconds 3600
exit
!

interface tunnel3
 no shutdown
 tunnel source 200.1.5.1
 tunnel destination 200.2.5.2
 tunnel protection ipsec profile IPSec_PROFILE_1_2
 ip address 10.0.3.1/24
exit
!
interface tunnel4
 no shutdown
 tunnel source 200.1.5.1
 tunnel destination 200.3.5.3
 tunnel protection ipsec profile IPSec_PROFILE_1_3
 ip address 10.0.4.1/24
exit
!
interface tunnel5
 no shutdown
 tunnel source 200.1.5.1
 tunnel destination 200.4.5.4
 tunnel protection ipsec profile IPSec_PROFILE_1_4
 ip address 10.0.5.1/24
exit

تابعی برای ساختن تونل wireguard

add-wg()
{
if [ "$#" -ne 4 ]; then
    echo "need argumenst : $0 <destination_ip> <my_id> <peer_id> <peer-pub-key>"
    return 1
fi

echo $1 $2 $3 $4



tunnel_id=$(($2+$3))
tunnel_ip="10.10.$tunnel_id.$3/32"
port=$((1000+$tunnel_id))

echo tunnel_id=$tunnel_id
echo tunnel_ip=$tunnel_ip

prvkey="wg$2-$3"

crypto key gen x25519 label $prvkey
my_pubkey=$(do sh crypto key $prvkey json | jq -r 'to_entries [] | .value[0].public_key')

interface wireguard$tunnel_id
 wireguard source 0.0.0.0
 wireguard private-key $prvkey
 wireguard port $port
 wireguard peer $1
  endpoint $1 port $port
  allowed-ip 0.0.0.0/0
  public-key $4
 no shutdown
 ip address $tunnel_ip
 wireguard peer $1
!
echo my public_key:  $my_pubkey
}

اگر مجدد سناریوی که تونل ipsec ایجاد کردیم را در نظر بگیریم می توانیم با تابع فوق تونل های wireguard بصورت fullmesh بین روتر ها ایجاد نماییم . برای این کار کافی است در هر روتر ابتدا یک کلید wireguard بسازیم تا pub-key آن را به عنوان ورودی به تابع به همراه دیگر آرگومان ها اختصاص دهیم :

ipsec-fullmesh
n1/config# crypto key generate x25519 label wg1
n1/config# do sh crypto key wg1
Keypair Label: wg1
  Algorithm:   X25519
  Public key:  9213E1463CBEA9A28A9E91A2659E32C1EDFDEDC2AF956D79D22F4482A6FBD769
  Public key base64:  khPhRjy+qaKKnpGiZZ4ywe397cKvlW150i9Egqb712k=
------------------------------------
n2/config# crypto key generate x25519 label wg2
n2/config# do sh crypto key wg2
Keypair Label: wg2
  Algorithm:   X25519
  Public key:  0980BC9E8F7E8E45FE2DC4F6B845B161F8B4BB3F21EFB01B5E3502867C669F10
  Public key base64:  CYC8no9+jkX+LcT2uEWxYfi0uz8h77AbXjUChnxmnxA=
------------------------------------
n3/config# crypto key generate x25519 label wg3
n3/config# do sh crypto key wg3
Keypair Label: wg3
  Algorithm:   X25519
  Public key:  CAA953CF7B569365DC7F6728308E59B2743FF4C2C97435C6DA7CA8D7FC9F5036
  Public key base64:  yqlTz3tWk2Xcf2coMI5ZsnQ/9MLJdDXG2nyo1/yfUDY=
------------------------------------
n4/config# crypto key generate x25519 label wg4
n4/config# do sh crypto key wg4                
Keypair Label: wg4
  Algorithm:   X25519
  Public key:  256AD17ED4ACB5F879A649F4ACA2AD07EE5E7B37982169F26536FB6655E3B257
  Public key base64:  JWrRftSstfh5pkn0rKKtB+5eezeYIWnyZTb7ZlXjslc=

n4/config# 

n1/config# add-wg 200.2.5.2 1 2 0980BC9E8F7E8E45FE2DC4F6B845B161F8B4BB3F21EFB01B5E3502867C669F10
n1/config# add-wg 200.4.5.4 1 4 256AD17ED4ACB5F879A649F4ACA2AD07EE5E7B37982169F26536FB6655E3B257
n1/config# add-wg 200.3.5.3 1 3 CAA953CF7B569365DC7F6728308E59B2743FF4C2C97435C6DA7CA8D7FC9F5036
n2/config# add-wg 200.1.5.1 2 1 9213E1463CBEA9A28A9E91A2659E32C1EDFDEDC2AF956D79D22F4482A6FBD769
n2/config# add-wg 200.4.5.4 2 4 256AD17ED4ACB5F879A649F4ACA2AD07EE5E7B37982169F26536FB6655E3B257
n2/config# add-wg 200.3.5.3 2 3 CAA953CF7B569365DC7F6728308E59B2743FF4C2C97435C6DA7CA8D7FC9F5036
n3/config# add-wg 200.1.5.1 3 1 9213E1463CBEA9A28A9E91A2659E32C1EDFDEDC2AF956D79D22F4482A6FBD769
n3/config# add-wg 200.4.5.4 3 4 256AD17ED4ACB5F879A649F4ACA2AD07EE5E7B37982169F26536FB6655E3B257
n3/config# add-wg 200.2.5.2 3 2 0980BC9E8F7E8E45FE2DC4F6B845B161F8B4BB3F21EFB01B5E3502867C669F10
n4/config# add-wg 200.1.5.1 4 1 9213E1463CBEA9A28A9E91A2659E32C1EDFDEDC2AF956D79D22F4482A6FBD769
n4/config# add-wg 200.3.5.3 4 3 CAA953CF7B569365DC7F6728308E59B2743FF4C2C97435C6DA7CA8D7FC9F5036
n4/config# add-wg 200.2.5.2 4 2 0980BC9E8F7E8E45FE2DC4F6B845B161F8B4BB3F21EFB01B5E3502867C669F10

بدین شکل ۱۲ تونل wireguard بین روتر ها تنظیم می شود .