<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های سینا قادری</title>
        <link>https://virgool.io/feed/@sina632</link>
        <description>توسعه دهنده و مهندس شبکه</description>
        <language>fa</language>
        <pubDate>2026-06-07 14:53:21</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/1338192/avatar/CMrsMa.jpeg?height=120&amp;width=120</url>
            <title>سینا قادری</title>
            <link>https://virgool.io/@sina632</link>
        </image>

                    <item>
                <title>مدل M-P-G گولنگ: الگوریتم زمان بند رانتایم چطور کار میکند</title>
                <link>https://virgool.io/@sina632/%D9%85%D8%AF%D9%84-m-p-g-%DA%AF%D9%88%D9%84%D9%86%DA%AF-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%B2%D9%85%D8%A7%D9%86-%D8%A8%D9%86%D8%AF-%D8%B1%D8%A7%D9%86%D8%AA%D8%A7%DB%8C%D9%85-%DA%86%D8%B7%D9%88%D8%B1-%DA%A9%D8%A7%D8%B1-%D9%85%DB%8C%DA%A9%D9%86%D8%AF-ki9cahweg1pq</link>
                <description>این پُست از وبلاگ توسعه دهندگان blog.snix.ir کپی برداری شده استخب امروز توی یکی از گروه های گولنگ دیدم در مورد عملکرد گوروتین ها و تفاوت هاشون با ترد یا نخ های سیستم عامل بحثی مطرح شده بود که تصمیم گرفتم یه بار واسه همیشه این پست رو اوکی کنم تا ابهامات در این مورد برطرف بشه.. توی این پست در وحله اول تفاوت های thread های هسته با goroutine ها رو برسی میکنیم و بعد از اون مدل زمان بند MPG رو.. خب کارمون رو از پایه شروع میکنیم قبل از اون اگه درس سیستم عامل رو پاس کرده باشید (با یه استاد درست حسابی) قائدتا این چیزایی که قراره توضیح بدیم رو بهتر متوجه میشید. البته نگران نباشید، من همه تلاشم رو میکنم که به زبون ساده این موارد رو مطرح کنم.اوکی برگردیم به خونه اول: مفهوم ترد یا نخ یا thread چیه؟ نخ ها به زبون ساده متون اجرایی هستند که شامل اطلاعاتی میشه که یه cpu واسه اجرای یک سری از دستورالعمل ها بهشون نیاز داره.. فرض کنید دارید یه کتابی رو میخونید، اما لازمه که یه استراحتی بکنید. برای اینکه بتونید بعدا خوندن این کتاب رو ادامه بدید لازمه که شماره صفحه و خط و کلمه ای که تا اینجای کار خوندید رو یادتون باشه. پس متون اجرایی یا به اصطلاح execution context این کتاب میشه شماره صفحه، شماره خط و شماره کلمه.. اگه یه همکلاسی داشته باشید که از همین روش واسه خوندن این کتاب استفاده کنه، پس وقتی که شما دارید استراحت میکنید این همکلاسی شما میتونه بدون اینکه مشکلی برای شما پیش بیاد از کتاب استفاده کنه.. مفهوم thread ها هم دقیقا همینه.. یک cpu (وقتی میگیم یک cpu یعنی یک واحد پردازشی نه یه cpu با ۲۰ تا هسته.. منظورمون یکی از هسته هاشه) با استفاده از همین روش و اجرای یه مقدار از دستورالعمل های این thread ها در بازه زمانی خیلی کم، توهم اجرای همزمان همه task ها در یک زمان رو ایجاد میکنه، درصورتی که اینطور نیست. در یک زمان فقط یک نخ میتونه از cpu استفاده کنه یا به اصطلاح به سی پی یو dispatch بشه.. اما cpu باید یادش باشه که کار فلان thread رو تا کجا ادامه داده که واسه دفعه بعدی از همونجا شروع به پردازش کنه.. به ذخیره کردن این اطلاعات و رفتن به سراغ thread بعدی میگن تعویض متن یا context switching.. خیلی ساده هست نه؟ با این روش ساده نخ ها میتونن در مدت زمان های بسیار کوتاه به سی پی یو dispatch و پردازش بشن جوری که انگار همه با هم در حال اجرا شدن هستن (همروندی)اما thread با پروسه چه فرقی میکنه؟ به طور خلاصه پروسه ها شامل حداقل یک و حداکثر X نخ میشن.. نخ های یک پروسه همه با هم یک فضای حافظه مجازی رو به اشتراک میزارن.. تعویض متن کوتاه تر و ساده تری نسبت به پروسه ها دارن و ارتباط دادنشون باهم ساده هست.. البته نخ ها استک اختصاصی خودشون رو هم دارن که توی اون فضای حافظه اشتراکی ایجاد میشه.. ما برای thread ها و زمان بند های هسته لینوکس یه پست دیگه اوکی کردیم که قبل از این پست پیشنهاد میشه یه نگاهی هم به اون بندازید..زمان بند چیه؟ خب گفتیم که نخ ها در مدت زمان کوتاهی به سی پی یو dispatch و پردازش میشن.. اما بر اساس چه ترتیبی؟ کدوم نخ اولویت بیشتری داره؟ کدوم نخ باید مدت زمان بیشتری پردازش بشه؟ و ... همه این سوالات رو زمان بند پاسخ میده، به طور خلاصه کارش مدیریت این نخ ها و dispatch کردنشون به cpu هست..خب بریم سراغ بحث اصلی: تفاوت نخ های هسته و نخ های سطح کاربر یا همون goroutine ها توی go چیه؟نخ های سطح هسته:توسط هسته سیستم عامل و زمان بند هاش مدیریت میشنتعویض متن طولانی تری دارنمقدار پیش فرض حافظه ای (استک) که بهشون اختصاص داده میشه حدودا یک مگابایت یا بیشتر هستتعداد بالای این نخ ها برای هر پروسه ممکنه مشکل ساز بشه.. (نمونش وب سرور اپاچی توی حالت fork)نخ های سطح کاربر:توسط رانتایم در سطح یوزر و توسط خود پروسه مدیریت میشنسیستم عامل ازشون خبر دار نمیشهتعویض متن کم هزینه و بهینه تری نسبت به نخ های هسته دارنحافظه مورد نیازی (استک) در حدود چند کیلوبایت برای شروع لازم دارن و در صورت نیاز میتونه بیشتر بشهبا مکانیزم هایی شبیه به epoll در لینوکس یا kqueue در bsd ها برای io بلاک میشنتوی golang بهشون گوروتین میگن البته با greenthread یا coreroutine ها هم شناخته میشن.. اما گوروتین ها یکم خاص تر هستند و تفاوت هایی دارنمیتونیم چندین میلیون از این نخ ها رو ایجاد کنیم بدون اینکه مشکلی پیش بیادتوی گولنگ خصوصا: استفاده ازشون آسون تره و زمانبندی منعطف تر هستگوروتین ها توسط زمان بند رانتایم به thread های سیستم عامل یا هسته map میشنخیلی خب تا اینجا فهمیدیم که تفاوت نخ های سیستم عامل و goroutine ها چیا هستن، از این به بعد الگورتیم زمان بندی M-P-G رانتایم گولنگ رو بررسی میکنیم و خواهیم فهمید که goroutine ها چطور توسط رانتایم گولنگ مدیریت و اجرا میشن...همونطور که از اسم این مدل زمانبندی مشخص هست شامل سه استراکت M و P و G میشه.. این سه حرف مخفف:حرف G: نشون دهنده یک گوروتین هست و شامل فیلد های ضروری مورد نیاز میشه مثله فیلد های current state و stack trace.. همچنین یه فیلد دیگه هم داره که رفرنسی هست به کدی که این گوروتین اجراش میکنه..حرف P: مخفف proccess هست.. در واقع GOMAXPROCS مشخص کننده تعداد این بخش هست.. لازمه بدونید قبل از go1.5 این مقدار همیشه ۱ میبود اما در ورژن های جدید تر گولنگ این مقدار تعداد هسته های cpu سخت افزار هست.. مقدار این متغیر قابل تغییر هست با استفاده از runtime.GOMAXPROCS(n int) میتونید تعداد این متغیر رو تغییر بدید.. افزایش تعداد P ها شاید برای سرویس های io bound فکر خوبی باشه.. اما جدای از اینها proccess یعنی چی؟ به طور خلاصه این proccess یک broker هست. یعنی کارش اینه که گوروتین ها رو به نخ های سطح هسته dispatch کنه، به زبون دیگه گوروتین ها رو به نخ های هسته map کنه.. یادمون باشه که گوروتین ها بدون اینکه به نخ های سطح هسته map بشن، به خودی خود واقعا کاری نمیتونن بکنن. این استراکت روی heap الوک میشه و شامل یک صف fifo میشه که بهش local run queue میگن..حرف M: مخفف machine هست که به معنی ماشین مجازی یا همون نخ های سطح هسته هست.. تعدادشون رو میشه تغییر داد و مشخص کرد حداکثر تعدادشون چقدر باشه.. اما شاید فکر زیاد جالبی نباشه.. این کارو میشه با استفاده از پکیج debug و فانکشن debug.SetMaxThreads(n int) انجام داد.. به صورت دیفالت این مقدار 10000 هستنکته در مورد GOMAXPROCS: تغییر این مقدار در زمان اجرا باعث میشه که روند اجرای برنامه به صورت همروند متوقف بشه.. یه اصطلاحی روش گذاشتن: دنیا واسه یه لحظه stop/start میشه.. (شروع روند gc هم برای یک لحظه همین کارو میکنه)// GOMAXPROCS sets the maximum number of CPUs that can be executing
// simultaneously and returns the previous setting. It defaults to
// the value of runtime.NumCPU. If n &lt; 1, it does not change the current setting.
// This call will go away when the scheduler improves.
func GOMAXPROCS(n int) int {
	if GOARCH == &amp;quotwasm&amp;quot &amp;&amp; n &gt; 1 {
		n = 1 // WebAssembly has no threads yet, so only one CPU is possible.
	}

	lock(&amp;sched.lock)
	ret := int(gomaxprocs)
	unlock(&amp;sched.lock)
	if n &lt;= 0 || n == ret {
		return ret
	}

	stopTheWorldGC(&amp;quotGOMAXPROCS&amp;quot)

	// newprocs will be processed by startTheWorld
	newprocs = int32(n)

	startTheWorldGC()
	return ret
}تصویر زیر نمای دیداری کلی از زمان بند رانتایم هست.. اگه دقت کنید متوجه میشید ک شامل دو نوع صف global run queue و local run queue میشه..در این صف ها که از نوع fifo هستن شامل گوروتین هایی میشن که در حالت runnable هستن.. قبل از اینکه ادامه بدیم.. حالت های گوروتین ها از نظر اجرا:در حال اجرا: گوروتین به یکی از M ها attach شده و در حال اجراستقابل اجرا یا runnable: گوروتین از حالت بلاک بیرون اومده و میتونه اجرا شه .. در یکی از صف های گلوبال یا لوکال قرار داره..مسدود یا block: گوروتین به دلیلی بلاک شده..‌اما بلاک شدن یک گوروتین میتونه دلایل مختلفی داشته باشه.. لیست زیر یک سری از دلایلی هست که ممکنه گوروتین بخاطرش بلاک و روند اجراش متوقف شده باشهچنل ها.. ارسال یا دریافت روی یه چنل یا select و ..mutex ها WaitGroup و غیره..Network و IO و تایمر هاSyscall هایی که زده میشه.. مثله باز کردن فایلروند اجرای GC یا همون اشغال جم کن..تغییر مقدار gomaxprocs در هنگام اجرازمان بند توی تایم اسلایس هایی در حدود هر 10 میلی ثانیه برای جلوگیری از به وجود اومدن گرسنگی گوروتین در حال اجرا رو به صف runnable منتقل میکنه.. سوال اصلی اینه که چرا باید اینکارو انجام بدیم؟ خب یه مثال ساده.. فرضا یه گوروتین دارید که به این صورت تعریف شده..func main() {
	go infLoop()
}

func infLoop() { for {} }توی مثال بالا گوروتین infLoop واسه هیچی بلاک نمیشه.. پس به همین دلیل همیشه به M یا همون thread سیستم عامل attach میمونه و باعث گرسنگی میشه.. به همین دلیل متوقف کردنش بعد از یک مدت ضروریه، از اونجایی که گوروتین ها context switch کم هزینه ای دارن این مدت زمان 10 میلی ثانیه مشکل ساز نمیشه ‌‌ ‌ ‌‌‌‌‌‌‌ مثال بالا رو میتونید به صورت زیر زمان بند فرندلی کنید.. به زبون دیگه میتونید به زمان بند بگید که هوای بقیه گوروتین ها رو هم داشته باش.. البته همونطور که گفتم زمان بند بعد از 10 میلی ثانیه بیخیال این گوروتین میشه و از حالت در حال اجرا به runnable تبدیلش میکنه..func main() {
   go infLoop()
}

func infLoop() { 
   for {
   	runtime.Gosched()
   } 
}خب تقریبا یکم با زمان بند رانتایم اشنا شدیم.. بیاید یکم جزئیات رو برسی کنیم.. همونطور که گفته شد زمان بند شامل دو صف گلوبال و لوکال میشه که هر P صف لوکال خودشو داره، این صف از نوع ارایه ای به طول 256 هست، پس طول صف لوکال 256 المنت میشه.. این فیلد که با نام runq تعریف شده رو میتونید اینجا ببینید.. البته ساختار P فقط شامل این صف نیست. درواقع علاوه بر این صف یک فیلد دیگه هم تعریف شده به اسم runnext که فقط یک گوروتین توش قرار میگیره.. و گوروتینی که توی این فیلد قرار میگیره همیشه گوروتین بعدی برای اجرا (attach کردن به M) هست.. (این فیلد میتونه nil هم باشه) .. اما سوالی که پیش میاد اینه که چرا ما به این فیلد نیاز داریم؟ برای جواب دادن باید یه مثالیو مطرح کنیم..var ch = make(chan string)
func a() {
    go b()
    &lt;-ch
}

func b() {
   ch &lt;- doIO()
}توی این مثال گوروتین a گوروتین b رو میسازه و اجرا میکنه، بعد خودش بلاک میشه برای سیگنال روی چنل که باید از b بیاد.. حالا b هم خودش یکار io باندیو انجام میده .. بدون runnext قائدتا b باید بره تو ته صف گلوبال یا ته صف لوکال همین P .. خب اگه این اتفاق بیوفته شاهد افت پرفورمنس هستیم .. چرا؟ چون a که بلاک هست و b ته صف .. a فقط تو بلاکی مونده به خاطر اینکه b نرسیده اول صف هنوز، پس این b بهتره که اول از همه اجرا شه که حداقل بیخود a توی حالت بلاک نباشه.. البته اینجا شاید بگین خب این باعث گرسنگی میشه.. چون اگه دوتا گوروتین همدیگه رو به اصطلاح spawn کنن پس همیشه توی این فیلد میمونن و بقیه گوروتین ها اجرا نمیشن.. خب قانون 10 میلی ثانیه رو یادتونه؟ دقیقا اینجا هم صدق میکنه و b فقط برای 10 میلی ثانیه اجرا میشه و بعدش دیگه میره تو صف گلوبال یا لوکال و فیلد runnext مقدارش nil میشه.. وقتی این فیلد nil باشه پروسسور یا همون P گوروتینی رو از صف گلوبال یا لوکال اجرا میکنه.. پس اینجا هم با اینکه preemption صورت میگیره باز هم گرسنگی پیش نمیاد..از طرفی صف گلوبال چون صفی هست که همه پروسسور ها بهش سر میزنن پس باید واسه دسترسی بهش یک قفلی هم اوکی کنیم.. منظور از قفل mutex هست، احتمالا با mutex اشنا باشید و ازش استفاده کرده باشید، دلیل وجود این قفل اینه که چون صف گلوبال منبع عمومی هست ممکنه دو پروسسور یا P به صورت هم زمان یک گوروتین رو روی دو thread یا همون M اجرا کن پس با این mutex مطمعن میشیم که این مشکل به وجود نمیاد..اما گوروتین ها بر چه اساسی توی این صف ها قرار میگیرن؟ مثلا من اگه یه گروروتین اوکی کنم تو کدوم صف میره؟اول وارد runnext میشه و به مدت یک تایم اسلایس (همون 10 میلی ثانیه) اجرا میشهبه local run queue همون P منتقل میشهدر صورتی که P کاملا پر شده بود، یعنی 256 تا گوروتین توی این صف داشتیم، نصفشون منتقل میشن به global run queueو سوال بعدی اینه که P ها چطور گوروتین ها رو از توی این صف ها انتخاب و اجرا میکنند؟ واسه جواب به این سوال لازمه یه قسمت از سورس رانتایم رو برسی کنیم، کد زیر قسمتی از فانکشن ()schedule هستif gp == nil {
		// Check the global runnable queue once in a while to ensure fairness.
		// Otherwise two goroutines can completely occupy the local runqueue
		// by constantly respawning each other.
		if _g_.m.p.ptr().schedtick%61 == 0 &amp;&amp; sched.runqsize &gt; 0 {
			lock(&amp;sched.lock)
			gp = globrunqget(_g_.m.p.ptr(), 1)
			unlock(&amp;sched.lock)
		}
	}
	if gp == nil {
		gp, inheritTime = runqget(_g_.m.p.ptr())
		// We can see gp != nil here even if the M is spinning,
		// if checkTimers added a local goroutine via goready.
	}
	if gp == nil {
		gp, inheritTime = findrunnable() // blocks until work is available
	}

	// This thread is going to run a goroutine and is not spinning anymore,
	// so if it was marked as spinning we need to reset it now and potentially
	// start a new spinning M.
	if _g_.m.spinning {
		resetspinning()
	}قبل از اینکه کد رو برسی کنیم باید بدونید که برای زمان بند یک کنتور یا counter تعریف شده به اسم schedtick که هر باری که زمان بندی انجام بشه این یکی اضافه میشه..خب به شرط اول دقت کنید.. منظور از gp همون گوروتین هست.. شرط اول میگه اگه گوروتین نداشتی وارد بلاک شو .. وقتی وارد بلاک شرط اول بشیم میبینیم که یک شرط دیگه هست.. این شرط میگه اگه counter زمان بند باقی ماندش به 61 صفر و طول صف گلوبال بیشتر از 0 بود بیا گوروتینی که میخوای اجرا کنی رو از صف گلوبال بردار .. همونطوری که میبینید عمل lock و unlock کردن mutex هم انجام میشه.. توی شرط دوم باز میاد gp رو چک میکنه که مبادا nil باشه.. اگه اینطور بود پس میاد یه گوروتین از صف لوکال همون P برمیداره.. در نهایت توی شرط سوم اگه بازم چیزی واسه اجرا نداشت، استراتژی work stealing و spinning در پیش گرفته میشه.. اینا رو در ادامه برسی میکنیم قبلش بیاید به صورت کلی این شروطی که بررسی کردیم رو مرور کنیم:بعد از هر 61 بار انتخاب گوروتین از صف لوکال یکبار هم از صف گلوبال انتخاب کناگه چیزی پیدا نشد برو واسه صف local run queueاگه بازم چیزی نبود سعی کن از بقیه P ها گوروتین بدزدی (توضیح میدیم بیشتر)اگه بقیه P ها هم آهی در بساط نداشتن دوباره صف global رو چک کناگه بازم چیزی نبود netpoller رو چک کن (توضیح میدیم درمودرش)از مورد پنجم به بعد M این P یا همون نخ سیستم عاملی که زیر دست این P هست به حالت spinning thread در میاد.. این بحث رو بعد از بررسی استراتژی hands off بیشتر توضیح میدیمتوی مورد سوم گفتیم دزدی اتفاق میفته، اما P ها چطور گوروتین های همدیگه رو کش میرن؟ خیلی ساده به صورت رندوم یه P رو انتخاب و نصف گوروتین های صف لوکالش رو برای خودشون انتقال میدنخب قبل از hand off و spinning و netpoller لازمه system call یا syscall ها رو بررسی کنیم.. system call ها چین؟ سیسکال ها درواقع درخواست انجام کار یا سرویسی هست که توسط user-space برای هسته سیستم عامل ارسال میشه.. خوندن فایل، بازکردن کانکشن، خاموش کردن سیستم و .. غیره همه با استفاده از syscall ها انجام میشن.. قبلا گفتیم که یکی از دلایلی که باعث بلاک شدن یک گوروتین میشه همین زدن سیستم کال هست.. اما یکم داستان پیچیده تر هست.. وقتی سیستم کال یک گوروتین توسط M یا نخ سیستم عامل اجرا بشه علاوه بر گوروتین، این M هم توی هسته بلاک میشه تا وقتی که syscall کامل بشه (مثلا فایل از رو دیسک سخت افزاری پیدا و باز بشه) خب از اونجایی که برنامه ما توی user-space ران میشه نمیتونیم بفهمیم الان این M یی که گیر انداختیم تو کرنل تا کی کارش تموم میشه، اوکی خب تا الان شاید با خودتون گفته باشید که پس تکلیف این P و گوروتین هایی که باید با این M اجرا بشن چی میشه؟ اینجاست که استراتژی hands off در پیش گرفته میشهاستراتژی hands off: قبل از اینکه M بخواد syscall یی رو اجرا کنه، باید M جایگزین دیگه ای پیدا کنه که جاشو پُر کنه.. تو این حالت P به اصطلاح assignment میشه به این M جدید و گوروتین ها میتونن اجرا شن.. الان M اصلی میتونه با خیال راحت syscall رو اجرا کنه.. بعد از کامل شدن syscall گوروتینی که براش بلاک شده بود به حالت runnable میره، اما تکلیف این M اصلی که syscall رو کامل کرده چی میشه؟ رانتایم به اصطلاح این M رو به حالت spinning یا park در میاره. از اونجایی که درخواست thread از سیستم عامل و از بین بردنش پرهزینه و زمان بره کار عاقلانه هم همین هست..پس وقتی syscall زده میشه نخ یا M جدید از کجا پیداش میشه؟از spinning thread ها استفاده میشه، قبلا گفتیم که اگه P توی صف ها گوروتینی واسه اجرا پیدا نکرده بود M یی که زیر دستش هست به این حالت در میاد، پس spinning thread میتونه توی چنین سناریو هایی به P ایده ال assign بشه.. اگه اخرین spinning thread یه کاری واسه انجام دادن پیدا کرد و خواست مشغول بشه، باید یک thread رو از سیستم عامل درخواست (unpark) کنه که اون thread شروع به چرخش کنهبازم اگه چیزی پیدا نشد از سیستم عامل درخواست میشهنخ های پارک شده چی هستن؟ نخ هایی هستن که توسط رانتایم از بین برده میشنچه زمانی thread ها در حالت spinning هستن؟نخ یا M یی که زیر دست P (یا assign شده به P) هست اما توی صف گلوبال، لوکال یا netpoller چیزی واسه اجرا پیدا نکرده باشه..نخ یا M یی که زیر دست P (یا assign شده به P) نیست..نخ یا M یی که توسط اخرین spinnig thread به اصطلاح unpark شده..و netpoller چیه؟ به صورت خلاصه یه نوع چک کننده نوتیفیکیشن یا event checker هست.. سرور شما میتونه به دو حالت کانکشن رو accept کنه، blocking mode یا non-blocking mod .. توی حالت blocking نخ یا thread در انتظار اینکه file descriptor کانکشن مورد نظر اماده بشه بلاک میمونه.. تو این حالت برای هر کانکشن یک نخ لازمه (وب سرور اپاچی توی مود fork)در حالت non-blocking که در لینوکس با epoll (سیسکال epoll_create) و در bsd ها با kqueue شناخته میشه، شما لازم نیست تا زمانی که file descriptor اماده بشه براش بلاک بمونید.. خود کرنل وقتی فلان file descriptor یا fd اماده خوندن شد بهتون خبر میده.. شما کافیه epoll_wait رو توی یک حلقه بینهایت ران کنید.. این epoll_wait وقتی event یی روی fd خاصی صورت بگیره خروجی میده بهتون .. و شما میتونید روی اون fd یا fd ها مثلا read انجام بدین .. همچنین epoll_ctl هم قابل استفاده هست ..توی گولنگ ما یک netpoller داریم که درواقع همین حلقه for هست که توش epoll_wait تعریف شده.. وقتی netpoller نوتیفیکیشنی از هسته دریافت میکنه که میگه میتونی عملیات io روی فلان fd انجام بدی، به اطلاعاتی که از قبل داره روجوع میکنه که ببینه ایا گوروتین هایی برای این fd بلاک شدن یا نه .. و بهشون اطلاع میده .. لازم به ذکره که netpoller کلا thread خودشو داره..به نظرتون میتونیم زمان بند رانتایم رو debug کنیم؟ یکم بفهمیم چی به چیه.. با ست کردن scheddetail=1,schedtrace=1000 برای env var یا همون متغیر GODEBUG میتونید اینکارو انجام بدید..تستش کنیم ببینیم چطور میشه.. یه برنامه hello-world اوکی میکنیمpackage main

import &amp;quotfmt&amp;quot

func main() {
	fmt.Println(&amp;quothello world&amp;quot)
}و در نهایت کامپایل و اجراش میکنیم..# go build main.go
# GODEBUG=scheddetail=1,schedtrace=1000 ./main
SCHED 0ms: gomaxprocs=8 idleprocs=5 threads=5 spinningthreads=1 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
  P0: status=1 schedtick=0 syscalltick=0 m=4 runqsize=1 gfreecnt=0 timerslen=0
  P1: status=1 schedtick=0 syscalltick=0 m=2 runqsize=0 gfreecnt=0 timerslen=0
  P2: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  P3: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  P4: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  P5: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  P6: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  P7: status=0 schedtick=0 syscalltick=0 m=-1 runqsize=0 gfreecnt=0 timerslen=0
  M4: p=0 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
  M2: p=1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=3 dying=0 spinning=false blocked=false lockedg=-1
  M1: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=2 dying=0 spinning=false blocked=false lockedg=-1
  M0: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=1
  G1: status=1(chan receive) m=-1 lockedm=0
  G2: status=1() m=-1 lockedm=-1
  G3: status=1() m=-1 lockedm=-1
  G4: status=4(GC scavenge wait) m=-1 lockedm=-1
hello worldلاین اول یک سری اطلاعات مهمی بهمون میده که به صورت خلاصه بررسیشون میکنیممقدار SCHED 0ms: مدت زمان اجرامقدار gomaxprocs=8: تعداد gomaxprocs هستمقدار idleprocs=5: پروسسور یا P هایی که سرشون شلوغ نیستمقدار threads=5: تعداد نخ هایی که توسط رانتایم مدیریت میشنمقدار spinningthreads=1: نخ هایی که درحالت چرخش هستنمقدار idlethreads=0: نخ هایی که سرشون شلوغ نیستمقدار runqueue=0: طول صف گلوبال هستمقدار gcwaiting=0: عمل اشغال جم کن یا gc اگه شروع بشه اجرا گوروتین ها متوقف میشه و این مقدار میشه یکمقدار nmidlelocked=0: تعداد M های lock شده که منتظر انجام کار هستنمقدار stopwait=0:‌مشخص کننده حالت دنیا هست.. مثلا اگه عدد 0x7fffffff بشه زمان بند گوروتین جدیدی رو start نمیکنهمقدار sysmonwait=0: مربوط به sysmon مانیتورینگ رانتایم میشه..به status گوروتین ها یا G ها توجه کنید.. این اعداد که از صفر تا 9 هستن مشخص کننده حالت گوروتین هستن.عدد 0 : Gidle هست، گوروتین الوکیت شده اما هنوز initialize نشده.عدد 1 : Grunnable هست، گروتین قابل اجرا هست و توی صف گلوبال یا لوکال قرار دارهعدد 2 : Grunning هست، گوروتین درحال اجرا هستعدد 3 : Gsyscall هست، گوروتین در حال انجام system call هستعدد 4 : Gwaiting، گوروتین در انتظار رانتایم هست.عدد 5 : Gmoribund_unused، در حال حاظر استفاده نمیشه و فقط توی سورس کد قرار داده شدهعدد 6 : Gdead، گوروتین exit شده یا تازه initialize شده باشهعدد 7 : Genqueue، در حال حاظر استفاده نمیشهعدد 8 : Gcopystack، استک گوروتین در حال انتقال هست و در صف ها قرار نمیگیرهعدد 9 : Gpreempted، گوروتین روند اجرای خودش رو برای یک گوروتین suspend شده متوقف میکنه.. اون گوروتین preempt میشهخیلی خب میشه گفت به صورت کلی تقریبا با زمان بند رانتایم گولنگ اشنا شدیم، البته خیلی از جزئیات مطرح نشدن چون در حدود این پُست نیستن و باید بیشتر به صورت تخصصی بهشون پرداخته بشه.. در نهایت امیدوارم از این پست لذت برده باشید.. کامنت و نظر رو فراموش نکنید.این پُست از وبلاگ توسعه دهندگان blog.snix.ir کپی برداری شده است</description>
                <category>سینا قادری</category>
                <author>سینا قادری</author>
                <pubDate>Tue, 22 Feb 2022 05:11:55 +0330</pubDate>
            </item>
                    <item>
                <title>برسی الگوریتم رمزنگاری rabbit و پکیج rabbitio در گولنگ</title>
                <link>https://virgool.io/golangpub/%D8%A8%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%D8%B1%DB%8C%D8%AA%D9%85-%D8%B1%D9%85%D8%B2%D9%86%DA%AF%D8%A7%D8%B1%DB%8C-rabbit-%D9%88-%D9%BE%DA%A9%DB%8C%D8%AC-rabbitio-%D8%AF%D8%B1-%DA%AF%D9%88%D9%84%D9%86%DA%AF-tg4gr75249bk</link>
                <description>این مطلب از وبلاگ توسعه دهندگان SNIX کپی برداری شده است... لینک پست درblog.snix.irتوی این دوره زمونه رمزنگاری اطلاعات یکی از مهم ترین بخش های دنیای دیجیتال هست، البته اهمیت رمزنگاری اطلاعات و استفاده از اون حتی به قبل از دنیای دیجیتال برمیگرده، شاید این جمله رو شنیده باشید که میگه information is power .. قطعا ارزش اطلاعات در هر برهه ای از تاریخ حائز اهمیت بوده، از زمان امپراطوری روم تا جنگ جهانی اول و انیگما در جنگ جهانی دوم و دوران جنگ سرد میتونیم اهمیت رمزنگاری اطلاعات و نقش اون توی تغییر سرنوشت بشریت رو مشاهده کنیم.قطعا رمزنگاری توی دنیای دیجیتال و خصوصا با گسترش اینترنت نقش خیلی پررنگ تری نسبت به قبل ایفا میکنه. برای مثال حتی همین پستی که شما دارید میخونید رمز شده.ولی رمزنگاری چیه؟ رمزنگاری در دنیای دیجیتال به معنی ناخوانا کردن اطلاعات برای افراد ثانویه با استفاده از الگوریتم های ریاضی هست. به صورتی که فرستنده و گیرنده بتونن این اطلاعات رو با یک یا چند کلید رمز و رمزگشایی کنن.تا به امروز قالب بر چند صد الگوریتم مختلف رمزنگاری منتشر شده که هرکدوم مزیت ها، مشکلات و نقص های امنیتی خودشونو دارن. توی این پست میخوایم الگوریتم رمزنگاری rabbit یا خرگوش رو مورد بررسی قرار بدیم.. به صورت خلاصه نحوه رمزنگاری اطلاعات توی این الگوریتم رو مرور کنیم و با یکی از الگوریتم های دیگه از نظر امنیت مقایسش کنیم و در نهایت پرفورمنس rabbit رو اندازه گیری کنیم.الگوریتم rabbit یا rabbit cipher (به الگوریتم رمزنگاری cipher میگن) در سال 2003 ارائه و در سال 2008 برای استفاده عموم منتشر شد. این الگوریتم که از نوع stream cipher و symmetric هست از اولش برای افزایش امنیت اطلاعات ارائه شد اما بعدا مشخص شد که علاوه بر امنیت، سرعت بسیار بالایی هم داره. قبل از اینکه ادامه بدیم:منظور از stream cipher چیه: همونطور که از اسمش پیداست به این معنیه که اطلاعات بایت به بایت رمز میشن.. برخلاف block cipher که یک بلوک از اطلاعات رو (مثلا 64 بیت) رمز میکنهمنظور از symmetric چیه: الگوریتم های رمزنگاری از نظر نوع کلید به دو دسته symmetric و asymmetric دسته بندی میشن. symmetric ها برای رمزگذاری و رمزگشایی از یک کلید استفاده میکنن مثله rabbit و asymmetric ها برای رمزگشایی از private key و برای رمزگذاری از public key استفاده میکنن مثله RSAمفهوم plain-text: اطلاعاتی که رمز نشده هستند و برای همه قابل خواندن هستمفهوم cipher-text: اطلاعاتی که توسط یک cipher رمز شده و برای همه قابل خواندن نیستمزیت رمزنگاری symmetric:بسیار سریع هستنبسیار امن هستن ( شکستن دیتا با استفاده از حمله Brute-force مدت زمان زیادی طول میکشه)طول cipher-text مساوی یا کمتر از plain-text هستمزیت رمزنگاری asymmetric:کلید خصوصی به اشتراک گذاشته نمیشهفرستنده و گیرنده و پیام تایید هویت میشهدر پروتکل TLS از هردو روش استفاده میشه... برای تایید هویت از RSA یا DSA و برای رمزنگاری از AES یا DES و جدیدا با chacha20نحوه رمزنگاری اطلاعات در الگوریتم rabbit: این الگوریتم برای رمزنگاری از یک کلید 16 بایتی و Initialization vector هشت بایتی استفاده میکنه. Initialization vector اختیاری هست. توی ورژن های اولیه Initialization vector وجود نداشت و به منظور امنیت بیشتر اضافه شد.این Initialization vector چیه: بعضیا بهش تاس هم میگن.. معمولا یک مقدار تصادفی هست که به منظور افزایش امنیت ازش استفاده میشه، به طور خلاصه کاری که انجام میده اینکه که اطمینان حاصل میکنه که cipher-text های تولید شده از یک plain-text شبیه به هم نباشن... مثال: فرض کنید hello-world رو با کلید secret-key رمز کردید.. اگه لازم باشه اینو چند بار روی یک کانکشن ارسال کنید، حمله کننده مرد میانی (شنود کننده اطلاعات یا فرد ثانویه) از متن پیام که hello-world هست با خبر نمیشه اما قطعا میفهمه که شما دارید یک plain-text ثابت رو چند بار ارسال میکنید. نقش iv اینجا مطرح میشه.. با استفاده از iv هر cipher-text تولید شده از یک plain-text ثابت با استفاده از کلید با مقدار قبلیش فرق میکنه. اگه از iv استفاده کنید باید با هر cipher-text این iv هم ارسال بشه.. البته ارسال این iv و مطلع شدن فرد ثانویه از این iv ها هیچ خطری نداره.امنیت اطلاعات در rabbit: از اونجایی که rabbit از کلید 128 بیت استفاده میکنه شکستن اطلاعات به روش brute-force مدت زمان غیر قابل تصوری طول میکشه.. برای مثال کل شبکه بیت کوین رو درنظر بگیرید، شکستن کلید rabbit برای این شبکه 70,000,000,000,000,000,000,000,000 سال طول خواهد کشید. حتی برای تکنولوژی های جدید اینده مثل کامپیوتر های کوانتومی هم شکستن این کلید به مدت زمان 12^10*2.61 سال طول میکشه ... برای اینکه بزرگی این مدت زمان ها دستتون بیاد لازمه بدونید کل سن جهان هستی از زمان بیگ بنگ تا الان تقریبا 10^10*1.38 تخمین زده شده...نحوه رمزنگاری اطلاعات: اندازه internal state این سایفر 513 بیت هست که شامل 8 متغیر 4 بایتی state و 8 متغیر 4 بایتی counter و یک بیت به عنوان carry میشه.تابع setup key: این تابع کلید 16 بایتی رو به 8 ارایه 2 بایتی تبدیل میکنه و با این ارایه ها متغیر های state و counter محاسبه میشن.به منظور کاهش ارتباط بین کلید و متغیر های internal state فانکشن next state چهاربار اجرا میشه..تابع setup iv: این تابع مقدار iv رو که 8 بایت هست با متغیر های counter که 8 بایت هستند xor میکنهبعد از این 4 بار دیگه فانکشن next state اجرا میشه تا متغیر های counter جدید با state ها محاسبه مجدد بشن.در فانکشن next state متغیر های counter هر بار با بیت carry و ثابت های rabbit (هشت ثابت چهار بایتی) sub32 میشن.. تابع sub32 برای محاسبه difference پارامتر اول، دوم و سوم استفاده میشه. توی این حالت پارامتر اول فیلد ارایه ثابت هاست و پارامتر دوم فیلد متناظر ارایه counter ها و پارامتر اخر هم carry هست..تابع extract: توی این تابع حالت های مختلف متغیرهای state با همدیگه xor میشن.. که در نهایت باعث تولید key stream میشه..رمزگذاری و رمزگشایی: هر بایت از key stream که اسلایسی به طول 16 هست با هر بایت از plain-text یا همون دیتای رمز نشده xor میشه و اون بایت از key stream حذف میشه... در نهایت وقتی طول key stream به صفر رسید دوباره extract انجام میشهبرای اطلاعات بیشتر میتونید به RFC 4503 و پروپوزال رسمی ارائه شده مراجعه کنید.پروژه eSTREAM و rabbit: پروژه eSTREAM که توسط EU ECRYPT (سازمان تحقیقاتی امنیت اطلاعات و رمزنگاری اتحادیه اروپا) حفظ میشه هدفش پیدا کردن و ارائه دادن cipher stream های بهینه و امن برای استفاده عمومی و گسترده هست. rabbit فاز سوم این پروژه رو پشت سر گذاشته و به همراه HC-128 و Salsa20/12 و SOSEMANUK به عنوان کاندید های نرم افزار مورد تایید قرار گرفته.‌‌‌‌ ‌‌‌‌‌‌مقایسه rabbit با الگوریتم های ضعیف تر، encrypt تصویر... : برای مقاسه من سایفر AES-ECB رو انتخاب کردم. و میخوام باهاش یک تصویر رو encrypt و نمایش بدم...چون میخوام تصویر رمز شده رو نمایش بدم پس نباید header های فایل رو رمز کنم... اگه کل فایل png رو رمز کنم header ها هم رمز میشن و فایل دیگه با اپلیکیشن های مشاهده تصویر باز نمیشه.. چون هدر ها هم تغییر کردن.. برای مشاهده تصویر رمز شده باید فقط اطلاعات مربوط به نحوه رنگ دهی پیکسل ها encrypt بشه.. البته png سختار پیچیده ای داره و من برای سادگی کار تصویر png رو به فورمت ppm که نسبتا ساده تر هست تبدیل میکنم...فرمت تصویر ppm: این فرمت شامل هدر و به دنبال اون دیتا میشه .. توی ورژن P3 هدر به صورت P3 1024 788 255 تعریف میشه و از لاین بعد کد رنگ هر پیکسل به صورت ascii تعریف میشه.. مثلا اگه این کد رو توی فایل text کپی کنید و با پسوند ppm ذخیره کنید، تصویر 3 در 4 پیکسل رو میتونید ببینید.P3 3 4 255 
255 0 0 0 255 0 0 0 255 255 255 0 255 255 255 0 0 0 0 255 255 75 75 75 127 127 127 150 150 150 150 150 150 150 150 150توی ورژن P6 این فرمت فایل اطلاعات به صورت باینری توی فایل ذخیره میشن.. که کار رو برای encrypt کردن دیتا راحت تر میکنه.. پس فایل png رو تبدیل به ppm P6 میکنم.خب من با دستور convert از پکیج imagemagick عکس png رو تبدیل به ppm میکنم:# apt install imagemagick
# convert logo.png logo.ppmواسه اینکه بفهمم طول هدر چند بایت هست این فایل logo.ppm رو با دستور xdd یا hexdump برسی میکنم..# hexdump -C logo.ppm | head -n 3
00000000  50 36 0a 33 38 34 20 33  38 34 0a 32 35 35 0a 00  |P6.384 384.255..|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|پس طول هدر این عکس 15 بایت هست و باید این 15 بایت رو از فایل جدا کنم و فایل رو با الگوریتم رمز کنم و دوباره هدر رو به اطلاعات رمز شده اضافه کنم..عکسی که انتخاب کردم اینه ... ‌‌تصویر اورجینال‌خب برای رمزنگاری این فایل با سایفر AES-ECB میخوام از openssl استفاده کنم و برای رمزنگاری با rabbit از کتابخونه snix.ir/rabbitio و زبان go استفاده میکنم.. این پکیج رو میتونید از git.snix.ir یا میرور گیت هاب دریافت کنیدبرای جدا کردن 15 بایت اول فایل logo.ppm توی لینوکس از دستور dd کمک میگیرم:# dd if=logo.ppm bs=1 count=15 of=header.bin
15+0 records in
15+0 records out
15 bytes copied, 0.000500669 s, 30.0 kB/sهدر رو توی فایلی به اسم header.bin ذخیره کردم... اما فایل logo.ppm هنوز header رو داره .. پس باید هدرش رو پاک کنیم که فقط اطلاعات رنگ بندی RGB باقی بمونه ... بعد از اینکار این فایل دیگه نباید با نرم افزار های نمایش عکس باز بشه...# dd if=logo.ppm bs=1 skip=15 of=temp-logo &amp;&amp; mv temp-logo logo.ppm
442368+0 records in
442368+0 records out
442368 bytes (442 kB, 432 KiB) copied, 1.22259 s, 362 kB/sو در نهایت با دستور openssl فایل logo.ppm رو رمز میکنیم.. یادمون باشه که فایل logo.ppm الان فقط شامل دیتای RGB هست..# cat logo.ppm | openssl enc -aes-128-ecb -K 6162636465666768696a6b6c6d6e6f70 -nosalt -out enc-rgb.data
# cat enc-rgb.data &gt;&gt; header.bin
# mv header.bin aes-ecb-logo.ppmتوی دستورات بالا دیتای RGB رو رمز کردیم و cipher-text به دست اومده رو به header.bin اضافه کردیم... و نام header.bin رو به aes-ecb-logo.ppm تغییر دادیم..تصویری که به دست اومد به این شکله... ‌‌تصویر encrypt شده با AES-128-ECB‌‌‌ و این عکس الان مصداق بارز الگوریتم رمزنگاری ضعیف هست... مفهوم تصویر کاملا مشخصه فقط شاید از لحاظ جزئیات یکم تغییر کرده باشه...حالا عکس اورجینال رو با استفاده از rabbit و با زبان گولنگ encrypt میکنیم.. تا ببینیم چه تفاوتی داره..package main
import (
	&amp;quotio&amp;quot
	&amp;quotos&amp;quot
	&amp;quotlog&amp;quot
	
	&amp;quotsnix.ir/rabbitio&amp;quot
)

const (
	headerLen = 15
)

var (
	key = []byte(&amp;quotabcd1234abcd1234&amp;quot) // key 16 byte
	ivx = []byte(&amp;quotrandom-x&amp;quot) // iv 8 byte
)

func main() {
	log.SetFlags(0) // just logs, no time and date... 

	// open logo-orginal.ppm file... 
	org, err := os.Open(&amp;quotlogo-orginal.ppm&amp;quot)
	if err != nil {
		log.Fatal(err)
	}
	
	
	// create a file to write cipher-text in it.
	enc, err := os.Create(&amp;quotlogo-rabbit.ppm&amp;quot)
	if err != nil {
		log.Fatal(err)
	}
	
	// write header to logo-rabbit.ppm without encryption...
	hnn, err := io.CopyN(enc, org, headerLen)
	if err != nil {
		log.Fatal(err)
	}
	
	log.Printf(&amp;quotppm header to file: %d byte copied&amp;quot, hnn)
	
	// new rabbit writer cipher .. 
	stream, err := rabbitio.NewWriterCipher(key, ivx, enc)
	if err != nil {
		log.Fatal(err)
	}
	
	// copy data through rabbit cipher.. 
	if _, err := io.Copy(stream, org); err != nil {
		log.Fatal(err)
	}
	
	log.Printf(&amp;quotdone *** &amp;quot)
}با استفاده از کامپایلر go کد زیر رو کامپایل و اجرا میکنیم...# nano main.go
# go mod init rabpic &amp;&amp; go mod tidy
# go build -o rabbit
# ./rabbit
ppm header to file: 15 byte copied
done ***و در نهایت تصویر به دست اومده به این شکل هست.. ‌ ‌‌تصویر encrypt شده با  Rabbit-128‌‌ ‌‌‌‌خب هیچ چیزی از تصویر اصلی مشخص نیست.. و تصویر کاملا encrypt شده.. برای decrypt کردن کافیه برنامه رو یکبار دیگه اجرا کنید.. البته برای عکس ورودی باید همین تصویری که رمز شده رو بدید ... اگه اینکارو انجام بدید خروجی برنامه تصویر اصلی میشه ...بررسی پرفورمنس rabbit: قبل از هر چیز پروژه eSTREAM یک بنچمارکی تهیه کرده که توش بیشتر cipher ها رو از نظر پرفورمنس با هم مقایسه کرده.. بنچمارک پردازنده pentium mاگه به لیست دقت کنید متوجه میشید که rabbit بین 10 الگوریتم برتر هست.. البته بیشتر این الگوریتم ها به دلیل اسیب پذیری هایی که داشتن نتونستن فاز های مربوطه eSTREAM رو با موفقیت بگذرونن و شکست خوردن. توی این لیست فقط HC-128 و HC-256 موقعیت برتری نسبت به rabbit دارن. البته الگوریتم های HC-128 و HC-256 یک مشکل پرفورمنسی دارن.. شاید از نظر stream کردن یه مقدار از rabbit سریع تر باشن اما فانکشن های setup iv این الگوریتم ها به شدت کُند هست.. پس برای رمزنگاری cipher-text های کوتاه اصلا مناسب نیستن..تا حالا که تا اینجای کار اومدیم بریم برای تست benchmark این cipher در گولنگ ... کد تست مربوطه به این صورت نوشته شده.. برای اینکه بتونیم مقدار زمان و منابعی که برای این الگوریتم صرف میشه رو محاسبه کنیم من دوتا تست اوکی میکنم، یک تست که فقط یک مقدار معینی از داده رو از یک بافر به بافر دیگه کپی میکنه و یک تست دیگه که علاوه بر کپی دیتا رو با rabbit cipher رمز هم میکنه.. در نهایت میتونیم با کم کردن زمان و منابع تست کُپی از تست rabbit مقدار تقریبی زمان و منابع رو مشخص کنیم..package rabbit_test

import (
	&amp;quotbytes&amp;quot
	&amp;quotio&amp;quot
	&amp;quotmath/rand&amp;quot
	&amp;quotstrings&amp;quot
	&amp;quottesting&amp;quot

	&amp;quotsnix.ir/rabbitio&amp;quot
)

const dataLEN = 2 &lt;&lt; 10 // 2 KB

var TEXT = makeRandomText(dataLEN)

func makeRandomText(n int) string {
	b := make([]byte, n)
	chr := []byte(&amp;quotJDDYDG&amp;quot)
	for i := range b {
		b[i] = chr[rand.Intn(len(chr))]
	}
	return string(b)
}

func BenchmarkRabbit(b *testing.B) {
	key := []byte(&amp;quotabcd1234abcd1234&amp;quot) // key 16 byte
	ivx := []byte(&amp;quotrandom-x&amp;quot)         // iv 8 byte
	msg := strings.NewReader(TEXT)
	bf := new(bytes.Buffer)
	cph, _ := rabbitio.NewWriterCipher(key, ivx, bf)
	if _, err := io.Copy(cph, msg); err != nil {
		b.Fatal(err)
	}
}

func BenchmarkCopy(b *testing.B) {
	msg := strings.NewReader(TEXT)
	bf := new(bytes.Buffer)
	_, err := io.Copy(bf, msg)
	if err != nil {
		b.Fatal(err)
	}
}و این نتیجه تست روی سیستم من هست.. توی کد بالا 2 کیلوبایت رشته رو کپی و رمز کردیم... نتایج تست روی سیستم های مختلف ممکنه متفاوت باشه# go test -v -bench=. -benchmem -count 4
goos: linux
goarch: amd64
pkg: rabpic
cpu: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
BenchmarkRabbit
BenchmarkRabbit-8       1000000000               0.0000122 ns/op               0 B/op          0 allocs/op
BenchmarkRabbit-8       1000000000               0.0000140 ns/op               0 B/op          0 allocs/op
BenchmarkRabbit-8       1000000000               0.0000124 ns/op               0 B/op          0 allocs/op
BenchmarkRabbit-8       1000000000               0.0000141 ns/op               0 B/op          0 allocs/op
BenchmarkCopy
BenchmarkCopy-8         1000000000               0.0000021 ns/op               0 B/op          0 allocs/op
BenchmarkCopy-8         1000000000               0.0000026 ns/op               0 B/op          0 allocs/op
BenchmarkCopy-8         1000000000               0.0000019 ns/op               0 B/op          0 allocs/op
BenchmarkCopy-8         1000000000               0.0000027 ns/op               0 B/op          0 allocs/op
PASS
ok      rabpic  0.014sدرنهایت امیدوارم از این پست لذت برده باشید.. کامنت و نظر فراموش نشه!این مطلب از وبلاگ توسعه دهندگان SNIX کپی برداری شده است... لینک پست درblog.snix.ir</description>
                <category>سینا قادری</category>
                <author>سینا قادری</author>
                <pubDate>Thu, 27 Jan 2022 10:43:23 +0330</pubDate>
            </item>
                    <item>
                <title>پیاده سازی پروکسی goshkan مشابه سایت شکن نوشته شده با گولنگ</title>
                <link>https://virgool.io/@sina632/%D9%BE%DB%8C%D8%A7%D8%AF%D9%87-%D8%B3%D8%A7%D8%B2%DB%8C-%D9%BE%D8%B1%D9%88%DA%A9%D8%B3%DB%8C-goshkan-%D9%85%D8%B4%D8%A7%D8%A8%D9%87-%D8%B3%D8%A7%DB%8C%D8%AA-%D8%B4%DA%A9%D9%86-%D9%86%D9%88%D8%B4%D8%AA%D9%87-%D8%B4%D8%AF%D9%87-%D8%A8%D8%A7-%DA%AF%D9%88%D9%84%D9%86%DA%AF-qbpyyzsyfzoq</link>
                <description> خب قبل از هرچیز، احتمالا با شکن (سایت شکن) اشنا هستید، شکن عملا یک سرور tls و http پروکسی هست که به صورت transparent یعنی بدون اینکه نیازی به کانفیگ اپلیکیشن های کلاینت باشه میتونه پروتکل tls و http رو پروکسی کنه. خب https همون http هست که بر بستر tls اطلاعات رو رمز میکنه پس وقتی میگیم tls شامل https هم میشه.سرویس شکن توی سایتش یه سری dns سرور معرفی کرده که با تنظیم اونها به عنوان resolver سیستمتون میتونید به قول خودشون تحریم ها رو دور بزنید. این dns سرور ها برای دامنه هایی که واسه ایران تحریم هستند به جای ادرس ایپی اصلی، ادرس سرور های پروکسی شکن رو برای شما resolve میکنن. به عنوان مثال query که شما برای resolve کردن دامنه golang.org به این dns سرور ها میفرستید به جای ادرس ایپی golang ادرس پروکسی سرور سایت شکن رو برای شما ارسال میکنه.سرور های پروکسی سایت شکن چطور کار میکنن؟ این سرور ها از تکنیک tls sni proxy برای پروکسی کردن https استفاده میکنن، sni یا server name indication فیلد palin-text یی هست که در بسته های hello پروتکل tls تعبیه شده. منظور از plain-text یعنی این فیلد رمز شده نیست.وقتی شما dns های شکن رو ست میکنید و کانکت میشید به سایت golang.org این نام دامنه به عنوان sni توی اولین بسته tls قرار میگیره و بسته به سمت مقصد ارسال میشه! منتها نه به سمت مقصد اصلی، این بسته برای سرور های شکن ارسال میشه، خب بعد از رسیدن این بسته به سرور x شکن، این سرور دوتا کار میتونه انجام بده یا بیخیال بسته بشه یا اونو به مقصد ارسال کنه، ولی از کجا باید بفهمه مقصد اصلی (که اینجا golang.org هست) چیه؟ درست حدس زدید. از فیلد sni توی بسته helloاز این به بعد سرور پروکسی اطلاعات دریافتی از شما رو روی کانکشنی که به سمت سایت golang زده شده مینویسه و اطلاعات دریافتی از سایت گولنگ رو روی کانکشنی که شما زدی! به همین سادگیولی سرور های شکن یه مشکلی دارن! توی پروتکل tls این فیلد sni فقط شامل نام دامنه میشه. پورت مقصد توش نیست. همین باعث میشه که سرور های شکن برای tls فقط بتونن روی یک پورت (۴۴۳) سرویس بدن. مثلا سایت golang.org اوکی هست اما اگه بخواید روی یک پورت دیگه به پروتکل tls وصل شید اینجاست که به مشکل میخورید. برای مثال سرور های شکن نمیتونن golang.org:9878 رو پروکسی کنن چون خود سرور رو این پورت listen نمیکنهخب این مشکل رو من توی goshkan حل کردم ، goshkan سرور پروکسی هست که میتونه روی همه 65535 پورت سرویس بده جدای از اینکه پورت مقصد چیه. به علاوه اینکه توی پروکسی کردن http منابع کمتری استفاده میکنیم. قبل از اون بزارید بپرسم: گولنگ که بلدید؟ درسته؟ چون از الان به بعد تقریبا کارمون توضیح کد های گولنگ هست! غیر از قسمت اخر که اجرا کردن و نصب goshkan روی لینوکس هست، برای این کار فقط باید کپی پیس کردن رو بلد باشید!‌اما goshkan چیکار میکنه؟ امکانات و قابلیت ها:اولا لازم هست http رو پروکسی کنیم (روی همه پورت ها)پروکسی tls که https هم جزعی ازش هست ضروریه (روی همه پورت ها)نمیخواییم کل دامنه ها رو پروکسی کنیم، به همین خاطر باید یه لیست سفید اوکی کنیم که مدیر سرور مشخص کنه چه دامنه هایی مجاز هستند، برای این کار البته باید از regex استفاده کنیمواسه کنترل و حذف و اضافه این regex پترن ها یه rest api ساده اوکی کنیم.بزارید روشنتون کنم! وقتی میگیم روی همه پورت ها منظورمون این نیست که واقعا روی همه پورت ها listen کنیم. پروکسی goshkan روی یک پورت لیسن میکنه و بقیه پورت ها رو با iptables براش redirect میکنیم. نگران نباشید بیشتر توضیح میدیم این بحث redirect رو.‌قبلا گفتم، این پروکسی یا کلا هر پروکسی دیگه ای بیس کارش اینه که یک کانکشن رو اکسپت یا دریافت کنه و در قبال این کانکشن یه کانکشن به سمت مقصد برقرار کنه و اطلاعات رو بین این کانکشن ها رد و بدل کنه.پس الان ما لازم داریم به یه tcp سرور که کانکشن های کلاینت رو accept کنه. پیاده سازی این tcp سرور توی گولنگ نسبتا ساده هست. کد زیر یک tcp سرور ساده هست که کانکشن ها رواکسپت و روی اونها hello world رایت میکنه.package main
import (
	&amp;quotnet&amp;quot
)

func main() {
	l, err := net.Listen(&amp;quottcp&amp;quot, &amp;quot127.0.0.1:7854&amp;quot)
	if err != nil { panic(err) }
	defer l.Close()
	for {
		conn, err := l.Accept()
		if err != nil { continue }
		go func(c net.Conn) {
			defer c.Close()
			c.Write([]byte(&amp;quothello world&amp;quot))
		}(conn)
	}
}‌ ‌ ‌ پیاده سازی این قسمت از کد توی goshkan به این صورت انجام شده ntcp.go لاین 108 ‌ ‌این داستان: ماست ها و خورشت ها‌تا اینجا ما فعلا کانکشن رو گرفتیم حالا باید از طرفش یه کانکشن به مقصد اصلی بزنیم. قبل از اون باید بگم که goshkan tls و http رو روی فقط یک پورت accept میکنه. منظورم اینه که مثلا یک پورت https و یک پورت جدا برای http نداریم. هردو این پروتکل ها روی یک پورت accept میشن. خب حدس میزنم تا الان باید با خودتون گفته باشید: چرا ماستا رو میریزی قاطی خورشتا؟ چطور میشه ما روی یک پورت هم http لیسن کنیم هم https (یا همون tls) در جواب باید بگم که شدنش رو که میشه بحث اینه که چطور ماستا رو بریزیم قاطی خورشتا بدون اینکه ماستا با خورشتا قاطی شن.از دید لایه transmission یا همون لایه پروتکل tcp پروتکل های لایه بالایی صرفا فقط دیتا هستند. برای مثال همین tcp سرور ساده خودمون رو در نظر بگیرید، شما میتونید روی کانکشنی که به این سرور زدید هر دیتایی رایت کنید. میتونید هدر http روش رایت کنید یا مثلا بسته hello پروتکل tls رو. پروتکل به زبون ساده ترتیب خاصی از دیتا و نحوه پردازش این دیتا هست. پس تا زمانی که این tcp سرور بتونه تشخیص بده این دیتایی که الان رایت شد ترتیب و چیدمانش به کدوم پروتکل میخوره، میتونیم چندین پروتکل رو روی یک پورت داشته باشیمبرای درک بهتر بزارید برگردیم به بحث ماست و خورشت: فرضا شما ۲ ظرف ماست و خورشت دارید و چشماتون بسته شده و نمیتونید ببینید اینا رو. چطور میتونید متوجه بشید که این ظرف (کانکشن tcp) توش ماست (tls) هست یا خورشت (http)؟ یه راه حل اینه که یکم از محتویات هر ظرف بخورید و مزش کنید. کاری که ما برای فهمیدن اینکه این دیتایی که داره رایت میشه tls هست یا http انجام میدیم.یکم بحث رو جدی تر کنیم: طبق RFC پروتکل tls اتصال این پروتکل همیشه با بسته های hello شروع میشه، از کجا بفهمیم این بسته hello هست؟ خب RFC گفته: بسته hello همیشه اولین بایتش 0x16 هست و بایت دوم و سوم مشخص کننده ورژن tls هستند. که به ترتیب 0x0301 برای ورژن 1.1 و 0x0302 برای ورژن 1.2 و 0x0303 برای ورژن 1.3 دسته بندی شدن. ولی RFC همچنین گفته که بسته های hello همیشه ورژنشون 0x0301 هست. یعنی کانکشن tls با 1.1 شروع میشه و اگه سرور/کلاینت اوکی بودند و ورژن های بالا تر ساپورت میشد، پروتکل به ورژن بالاتر تعیین شده تغییر پیدا میکنه. خلاصه اینا واسه ما مهم نیست. لب کلام: اگه سه بایت اول رایت شده رو کانکشن 0x160301 بود. این دیتا دیتای پروتکل tls هست.پیاده سازی این حرکت به این شکل توی goshkan انجام شده ntcp.go لاین 129func (str *proxyTLS) handleNewConn(conn net.Conn) {
	defer conn.Close()
	// setup an small buffer to capture packet signature
	pktsig := make([]byte, 3)
	if _, err := conn.Read(pktsig); err != nil {
		opts.CONNEC(err)
		return
	}

	// so is this a TLS connection or anything else? 0x16 hello 0x0301 tls version
	// based on tls RFC first packet version is always 0x0301
	if bytes.Equal(pktsig, []byte{0x16, 0x03, 0x01}) {
		str.handleTLSConn(conn, pktsig)
		return
	}

	// Handle HTTP Request
	str.handleHTTPConn(conn, pktsig)
}حالا بحث اینجاست: چطور sni رو بخونیم؟ خب اگه بازم RFC مطالعه بشه مشخص شده که sni از کجا تا کجاست. منتها راه حل اسون تر و البته با پرفورمنس پایین تر اینه که یک سرور tls روی go اوکی کنیم و کانکشنی که به tcp سرور زده شده رو بهش پاس بدیم. توی این tls server میتونیم با استفاده از فیلد GetConfigForClient توی tls.Config یک فانکشنی رو تعریف کنیم که بعد از هندشیک tls این sni رو برامون در بیاره. میدونم، حرکت جالبی نیست ولی خب افت پرفورمنس هم اونقدی نیست که بخایم بزنیم به دل دیتای tls رایت شده و به صورت raw این sni رو پیدا کنیم.برای دوستانی که هنوز مغزشون درگیره که این sni رو چرا لازم داریم؟ دیتای tls رو از کلاینت گرفتیم حالا به کجا باید بفرستیمش ؟ ادرس مقصد همون sni هست که نام دامنه مثلا فلان سایتی هست که کلاینت میخواد بهش وصل شه... پیاده سازی این حرکت به این شکل انجام شدهfunc (pr *sniTLSLoadHs) extractHost() (string, error) {
	var tlsdata string
	err := tls.Server(readOnly{reader: pr.te}, &amp;tls.Config{
		GetConfigForClient: func(tlshand *tls.ClientHelloInfo) (*tls.Config, error) {
			tlsdata = tlshand.ServerName // GetConfigForClient runs after handshake
			return nil, nil
		}}).Handshake() // just handshake

	if len(tlsdata) == 0 {
		return tlsdata, err // check tlsdata
	}
	return tlsdata, nil
}اوکی خب تا اینجای کار sni رو دراوردیم و میدونیم به چه ادرسی باید کانکت بشیم حالا باید کل پورت ها رو ریدایرکت کنیم روی پورتی که goshkan روش درحال listen کردن هست.. با این دستور iptables هر بسته tcp که روی اینترفیس ens3 به سرور رسیده باشه و مقصدش ادرس 192.168.122.149 که ادرس ایپی سرور هست رو ریدایرکت میکنیم رو پورت goshkan، اگه بسته ها از یک اینترفیس دیگه یا با یک ادرس مقصد دیگه به سرور برسن، ریدایرکت انجام نمیشه.# iptables -t nat -A PREROUTING -i ens3 -d 192.168.122.149 -p tcp -m tcp --dport 1:65535 -j REDIRECT --to-ports 8443توی دستور بالا همه پورت ها به پورت 8443 ریدایرکت میشن که پورتی هست که goshkan در حال لیسن کردن روش هست.. بعد از اجرای این دستور دیگه نمیتونیم روی این ادرس ایپی سرویسی ران کنیم، چون همه پورت ها ریدایرکت میشن! نکته: اگه این دستور رو روی سرورتون که بهش ssh زدید اجرا کنید و ادرس ایپی توی کامند همون ادرسی باشه که بهش ssh زدید، مطمعنا دهنتون سرویس میشه (شاید بلافاصله نه، چون کانکشنتون در حالت استابلیش هست) پس بهتره برای اجرای goshkan سرورتون دوتا ادرس ایپی داشته باشه یا حداقل قبل از اجرای دستور بالا پورت هایی که نمیخوایید ریدایرکت بشن مثله پورت 22 رو حتما ACCEPT کنید.# iptables -t nat -A PREROUTING -i ens3 -d 192.168.122.149 -p tcp -m tcp --dport 22 -j ACCEPT‌به نظر همه چی اوکی باشه البته همیشه یه مشکلی هست!مشکل اینجاست که وقتی ریدایرکت رو انجام میدیم، پورت مقصد بسته قبل از اینکه برسه به سوکت goshkan از هرچیزی که هست به 8443 تغییر پیدا میکنه (ریدایرکت در نهایت یه نوع dnat هست) مثلا اگه کلاینت به یک دامنه روی پورت 9878 کانکشن tls بزنه، وقتی (iptables) netfilter اینو ریدایرکت میکنه روی پورت 8443 و بسته میرسه به سوکت goshkan دیگه مقصد 9878 نیست و به 8443 تغییر پیدا کرده. از اونجایی که sni فقط ادرس هاست هست و شامل پورت نمیشه به طور خلاصه پورت اصلی مقصد رو گم میکنیم!البته منظور از گم کردن پورت این نیست که واقعا پورت گم شده، درواقع توی اپلیکیشن دیگه دسترسی به این پورت نداریم! کرنل و netfilter دقیقا میدونه پورت اصلی چیه، چون که توی جدل nat table و conntrack خودش اونو ذخیره کرده.. (در صورتی که ماژول nf_conntrack لود شده باشه) این جدول رو میتونید توی این مسیر /proc/net/nf_conntrack/ پیدا کنید.برای حل این مشکل چنتا روش وجود داره:روش اول اینه که سوکت رو روی لایه ایپی اوکی کنیم، یعنی دامنه سوکت رو از خانواده AF_INET تعریف کنیم، این حرکت بهمون اجازه میده توی لایه IP دسترسی پیدا کنیم به هدر های tcp اما اگه اینکارو انجام بدیم عملا باید کل استک tcp رو هم پیاده سازی کنیم که کارمون خیلی سخت میشه و اثر منفی روی پرفورمنس میزارهروش دوم که کلودفلر هم پیاده سازیش کرده استفاده از tproxy هست، خب روش خوبیه ولی یکم دردسر پیاده سازیش زیاد هست.روش سوم که روشی هست که goshkan ازش استفاده میکنه درواقع استفاده از unix.SO_ORIGINAL_DST هست که بهمون میگه ادرس اصلی کلاینت قبل از وارد شدن به زنجیره PREROUTE چی بوده! پیاده سازی این روش توی goshkan به این صورت انجام شده فایل dnat.go لاین 44func networkOpt(conn net.Conn) (*sockOpt, error) {
	var nftab = newNfTab()
	fd, err := conn.(*net.TCPConn).File&#40;&#41;
	if err != nil {
		return nftab, err
	}

	defer fd.Close()

	raddr, err := unix.GetsockoptIPv6Mreq(
		int(fd.Fd()), unix.IPPROTO_IP, unix.SO_ORIGINAL_DST)
	if err != nil {
		return nftab, err
	}

	nftab.localAddr = net.IP(raddr.Multiaddr[4:8])
	// convert big endian to little endian
	nftab.localPort = uint16(raddr.Multiaddr[2])&lt;&lt;8 + uint16(raddr.Multiaddr[3])
	return nftab, err
}توی کد بالا از unix.GetsockoptIPv6Mreq استفاده کردیم و file descriptor کانکشن مورد نظر رو بهش پاس دادیم، متغیر raddr که از نوع unix.IPv6Mreq هست شامل دو فیلد Interface و Multiaddr میشه، فیلد Multiaddr ارایه ای از بایت به طول 16 هست که دو بایت اول مشخص کننده نوع پروتکل هست و بایت سوم و چهارم پورت orginal رو مشخص میکنه. و بقیه بایت ها مشخص کننده ادرس dst اورجینال بسته هستند. ولی وقتی بایت سوم و چهارم رو برسی کنیم متوجه میشیم که هیچ جاش شبیه به پورت نیست.. مثلا بایت سوم 35 هست و بایت چهارم 29خب این همون پورت هست ولی ترتیب قرار گیری بایت ها به صورت big-endian هست، توی نتورک معمولا روش قرار گیری بایت ها به صورت big-endian هست و توی سیستم عامل به صورت little-endian... برای تبدیل big-endian به little میتونید از پکیج binary استفاد کنید.. یا به صورتی که توی کد میبینید این کارو خودتون انجام بدید... کافیه بایت سوم رو 8 بیت شیفت کنید به چپ و به اضافه بایت چهارم کنید، اگه این کارو روی 35 و 29 انجام بدید میبینید که مقدار به دست اومده 8989 میشه!خب با این حرکت goshkan میتونه روی همه پورت ها listen کنه و در نهایت بفهمه که مقصد اصلی بسته کجاست و به این مقصد اصلی که ترکیبی از مقدار sni و ادرس پورت به دست اومده هست کانکت بشه و اطلاعات دریافتی از کلاینت رو روی این کانکشن رایت کنه.خبر خوب اینه که برای http نیاز نیست پورت رو از این روش پیدا کنیم. فیلد Host پروتکل http خودش به صورت host:port هست که کارمون رو خیلی اسون میکنه...سرور goshkan اجازه دسترسی به هر هاست و دامنه ای رو نمیده و لیست سفید باید توسط ادمین و با استفاده از api برای سرور تعریف بشه.. ادمین شبکه میتونه از regex استفاده کنه تا دامنه های خاصی رو درون این لیست قرار بده.مشکلی که regex داره اینه که شاید یکم کند باشه! به خاطر همین goshkan این امکان رو داره که دامنه های match شده رو cache کنه.، goshkan بعد یکبار match شدن یک دامنه یا ادرس و برقراری اتصال موفق با سرور، نام دامنه رو به همراه یک تایمر (یا تیکر) توی مپ ذخیره میکنه که برای دفعه بعد دیگه نخواد باز بره سراغ regex .. البته همون طور که گفتم با یک تایمر ذخیره میشه، این تایمر که مقدارش توی کانفیگ تعریف میشه موقعی که به سر برسه خودش و دامنه رو از توی map حذف میکنه.. البته اینم باید بگم که اگه قبل از اینکه این تایمر به سر برسه کانکشن جدیدی به ادرس این دامنه برقرار بشه، این تایمر ریست میشه... به طور کلی دامنه هایی که کانکشکن و درخواست براشون زیاد هست و اتصالات زیادی به این دامنه ها برقرار میشه همیشه توی مپ میمونن چون بعد از هر کانکشن تایمر ریست میشه! این حرکت باعث میشه بیخود وقت و هزینه برای الگوریتم regex صرف نشه و به جاش از الگوریتم hashtable با پیچیدگی زمانی O(1) استفاده میشه.پیاده سازی این map و تایمر (تیکر) در goshkan به این صورت انجام شده فایل map.go‌‌ اوکی تا اینجا روش ها و تکنیک هایی که goshkan باهاشون نوشته شده رو توضیح دادیم، از این به بعد پیاده سازی سرور goshkan روی لینوکس debian و نصب و راه اندازی این سرویس به همراه mysql و dns سرور رو برسی میکنیم. پیشنهاد میشه از لینوکس دبین bullseye استفاده کنید.دانلود فایل اجرایی goshkan از گیت هاب:‌ ادرس قسمت release ها ... یا میتونید سورس رو clone و با استفاده از go build کامپایل کنید# wget https://github.com/Sina-Ghaderi/goshkan/releases/download/goshkan-0.2.6/goshkan.tar.xz
# tar -xvf goshkan.tar.xz &amp;&amp; chmod u+x goshkan
# mkdir -p /opt/goshkan/ &amp;&amp; mv goshkan /opt/goshkan/
# &gt; server-config.jsonخب فایل باینری (اجرایی) رو دانلود کردیم و دایرکتوری های مورد نظر رو ساختیم! الان موقع کانفیگ هست.. فایل کانفیگ بر مبنای json هست و اپشن های در دسترس اینها هستن..{
    &amp;quotMYSQL_PASSWORD&amp;quot: &amp;quotpassword&amp;quot,
    &amp;quotMYSQL_USERNAME&amp;quot: &amp;quotusername&amp;quot,
    &amp;quotDOMAIN_MEMTTL&amp;quot: 60,
    &amp;quotMYSQL_DATABASE&amp;quot: &amp;quotgoshkan&amp;quot,
    &amp;quotMYSQL_ADDRESS&amp;quot: &amp;quotlocalhost&amp;quot,
    &amp;quotCONNECT_TIMEOUT&amp;quot: 10,
    &amp;quotLISTEN_ADDRESS&amp;quot: &amp;quot192.168.122.149:8443&amp;quot,
    &amp;quotCLIENT_TIMEOUT&amp;quot: 15,
    &amp;quotHTTPAPI_LISTEN&amp;quot: &amp;quot127.0.0.1:8080&amp;quot,
    &amp;quotLOGS_DEBUGGING&amp;quot: true
}پارامتر MYSQL_PASSWORD: پسورد دیتابیس هستپارامتر MYSQL_USERNAME: یوزنیم دیتابیس هستپارامتر DOMAIN_MEMTTL: مشخص کننده زمان و طول عمر دامنه ها در map (همون cache) هست.. این مقدار درواقع مشخص کننده زمانی هست که برای تایمریی که در موردش توشیح دادیم هست. یکای این مقدار ثانیه هست و نباید عدد منفی باشه، اگه این مقدار رو صفر بزارید کلا goshkan قابلیت cache رو غیرفعال میکنه.. اگه مقدار ram در دسترس به اندازه کافی هست بهتره که صفر نزارید.پارامتر MYSQL_DATABASE: نام دیتابیسپارامتر MYSQL_ADDRESS: مشخص کننده ادرس دیتابیسپارامتر CONNECT_TIMEOUT: کانکشن time-out برای سرور هایی که بهشون کانکت میشیم. یکا ثانیه هست و نمیتونه صفر یا منفی باشهپارامتر CLIENT_TIMEOUT: مشخص کننده time-out برای کانکشن هایی هست که کلاینت ها به سرور goshkan میزنن. واحد ثانیه و نمیتونه صفر یا منفی باشهپارامتر HTTPAPI_LISTEN: ادرسی که api روش listen میکنه، چون این api احراز هویت نداره ادرس باید ادرس لوکال یا ادرسی باشه که به صورت ازاد در دسترس عموم نیست.. برای پیاده سازی احراز هویت برای این api میتونید از nginx یا apache استفاده کنیدپارامتر LOGS_DEBUGGING: فعال سازی debugging مقدار true یا falseپارامتر LISTEN_ADDRESS: ادرسی که پروکسی tls و http روش listen میکنناین کانفیگ رو توی فایل server-config.json ذخیره کنید و به دایرکتوری که goshkan توش قرار گرفته انتقال بدید# nano server-config.json
# mv server-config.json /opt/goshkan/فایل سرویس systemd رو ایجاد و سرویس goshkan رو بزارید توی استارت‌آپ سیستم.. واسه این کار اولا یک فایل با پسوند system بزازید و محتویات زیر رو توش کپی کنید# &gt; goshkan.service‌[Unit]
Description=Goshkan TLS HTTP Proxy Server

[Service]
ExecStart=/opt/goshkan/goshkan --config /opt/goshkan/server-config.json
WorkingDirectory=/opt/goshkan/
LimitNOFILE=32768

[Install]
WantedBy=multi-user.targetو برای start شدن سرویس هنگام boot دستور زیر رو اجرا کنید# mv goshkan.service /lib/systemd/system/
# systemctl daemon-reload
# systemctl enable goshkan.serviceفعلا سرویس رو استارت نکنید چون هنوز mysql رو نصب نکردیم. من به جای mysql از mariadb استفاده میکنم که توی دبین میشه با apt install mariadb-server نصبش کرد... بعد از نصب حتما یادتون باشه دستور mysql_secure_installation رو اجرا کنید.در نهایت با استفاده از دستور mysql -u root به سرور mariadb لاگین میشیم و دستورات زیر رو اجرا میکنیمCREATE DATABASE goshkan;
CREATE USER &#039;username&#039;@&#039;localhost&#039; IDENTIFIED BY &#039;password&#039;;
GRANT ALL PRIVILEGES ON goshkan.* TO &#039;username&#039;@&#039;localhost&#039;;

CREATE TABLE goshkan.regext (
	regexid INT UNSIGNED auto_increment NOT NULL,
	regexstr LONGTEXT NOT NULL,
	PRIMARY KEY (regexid)
)

ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_general_ci;
FLUSH PRIVILEGES;
EXIT;یادتون باشه که یوزرنیم و پسورد رو به مغادیر دلخواه تغییر بدید و حتما توی فایل server-config.json یوزرنیم و پسوردی که انتخاب کردید رو جایگزین کنید.سرور من دو ادرس ایپی روش ست شده یکی 192.168.122.149 و 192.168.122.150 که اولین ادرس رو اختصاص میدم به پروکسی و دومین ادرس رو برای مدیریت و بقیه سرویس ها استفاده میکنم... دستور iptables رو برای ریدایرکت کردن همه پورت ها اجرا میکنم# iptables -t nat -A PREROUTING -i ens3 -d 192.168.122.149 -p tcp -m tcp --dport 1:65535 -j REDIRECT --to-ports 8443بریم برای کانفیگ dns سرور... خب شکن برای dns از سرویس unbound استفاده کرده که ماهم از همین سرویس استفاده میکنیم، برای نصب این سرویس از دستور apt install unbound استفاده میکنیم.. بعد از نصب به مسیر زیر میریم و یک فایل جدید برای کانفیگ اینجاد میکنیم..# cd /etc/unbound/unbound.conf.d/
# nano goshkan.confخب داخل این فایل کانفیگ زیر رو کپی میکنیمserver:
## listen on secondary server ip address
interface: 192.168.122.150

## tag for domains
define-tag: &amp;quotgoproxy&amp;quot
access-control-tag: 0.0.0.0/0 &amp;quotgoproxy&amp;quot
## pass 192.168.122.149 (goshkan proxy) for goproxy tag domains.
access-control-tag-data: 0.0.0.0/0 &amp;quotgoproxy&amp;quot &amp;quotA 192.168.122.149&amp;quot



####### Domains List Goes Here ########
## new local-zone for snix.ir that should be redirected to goproxy
local-zone: &amp;quotsnix.ir&amp;quot redirect
local-zone-tag: &amp;quotsnix.ir&amp;quot &amp;quotgoproxy&amp;quot

local-zone: &amp;quotkernel.org&amp;quot redirect
local-zone-tag: &amp;quotkernel.org&amp;quot &amp;quotgoproxy&amp;quotتوی کانفیگ بالا دامنه های snix.ir و kernel.org رو ریدایرکت کردیم روی 192.168.122.149... یعنی query که این سرور برای این دامنه ها پاسخ میده رکورد A 192.168.122.149 هست که ادرس ایپی پروکسی goshkan درحال listen کردن روی همه پورت ها هست... خود unbound از regex پشتیبانی نمیکنه ولی میشه ماژول هایی شبیه به این براش اوکی کرد که بشه فیلترینگ دامنه رو بر اساس regex انجام داد.خب اینم از کانفیگ dns و دامنه ها... قبل از اینکه بریم برای تست یه نکته امنیتی هست که باید مد نظر قرار بدید! هیچ وقت ادرس دامنه localhost یا 127.0.0.1 یا دامنه های سرور و ادرس ایپی هاش رو توی goshkan اضافه نکنید.. چون ممکنه باعث connection-loop یا دسترسی غیر مجاز کاربر تایید هویت نشده به منابع حساس سرور بشه. برای محکم کاری میتونید از دستور زیر استفاده کنید تا مطمعن بشید که connection-loop پیش نمیاد# iptables -t mangle -A PREROUTING -i ens3 -s 192.168.122.149 -d 192.168.122.149 -p tcp -j DROPاوکی نوبت تست هست، همونطور که قبلا گفته شد برای اضافه کردن دامنه به لیست goshkan باید با استفاده از api اینکارو انجام بدید، داکومنت api رو میتونید از این لینک مطالعه کنید..اضافه کردن دامنه snix.ir و نمایش لیست pattern ها در goshkan با استفاده از curl از روی سرور:# curl -d &#039;{&amp;quotRegex&amp;quot:&amp;quotsnix.ir&amp;quot}&#039; -X POST localhost:8080/api/add/
# curl -X GET localhost:8080/api/all/
[
	{
		&amp;quotRegex&amp;quot: &amp;quotsnix.ir&amp;quot,
		&amp;quotRegID&amp;quot: 9
	}
]برای اتصال به snix.ir من از یک هاست دیگه ای که ادرس ایپی 192.168.122.1 رو به خودش اختصاص داده استفاده میکنم، سرور resolver این هاست روی 192.168.122.150 تنظیم شده و وقتی پینگ snix.ir رو بگیرم ادرس 192.168.122.149 رو برام برمیگردونه!# ping -c 4 snix.ir
PING snix.ir (192.168.122.149) 56(84) bytes of data.
64 bytes from snix.ir (192.168.122.149): icmp_seq=1 ttl=64 time=0.652 ms
64 bytes from snix.ir (192.168.122.149): icmp_seq=2 ttl=64 time=0.647 ms
64 bytes from snix.ir (192.168.122.149): icmp_seq=3 ttl=64 time=0.638 ms
64 bytes from snix.ir (192.168.122.149): icmp_seq=4 ttl=64 time=0.924 ms

--- snix.ir ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3073ms
rtt min/avg/max/mdev = 0.638/0.715/0.924/0.120 msبرای تست پورت روی سرور snix.ir چنتا سرویس اوکی میکنم... یک سرویس http روی پورت 8990 که این http سرور کارش فقط redirect کردن روی https://snix.ir:9898 هست و یک سرویس https که روی 9898 لیسن میکنه و بعد از اون از هاستی که ادرسش 192.168.122.1 هست بهش وصلش میشیم... تا ببینیم goshkan درست کار میکنه یا نه..# wget http://snix.ir:8990 
--2021-11-18 00:26:38--  http://snix.ir:8990/
Resolving snix.ir (snix.ir)... 192.168.122.149
Connecting to snix.ir (snix.ir)|192.168.122.149|:8990... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://snix.ir:9898/ [following]
--2021-11-18 00:26:38--  https://snix.ir:9898/
Connecting to snix.ir (snix.ir)|192.168.122.149|:9898... connected.
HTTP request sent, awaiting response... 200 OK
Length: 53 [text/html]
Saving to: ‘index.html’

index.html                                     100%[=================================================================================================&gt;]      53  --.-KB/s    in 0s      

2021-11-18 00:26:39 (49.7 MB/s) - ‘index.html’ saved [53/53]

# cat index.html
ITS WORKING... HELLO FROM SNIX.IR ON HTTPS PORT 9898اگه دقت کنید متوجه میشید که در وحله اول با http به پورت 8990 متصل شدیم و ریدایرکت شدیم به پورت 9898 و همچنین ادرس ایپی 192.168.122.149 هست..از اونجایی که دیباگینگ goshkan فعال بود، لاگ کانکشن رو هم برامون انداخته..2021/11/17 20:55:23 SYSLOG: parsing domain database and compiling regex string patterns
2021/11/17 20:55:23 APISRV: starting api service at: 127.0.0.1:8080
2021/11/17 20:55:23 NETLOG: starting tls and http proxy, listening on: 192.168.122.149:8080
2021/11/17 20:55:44 CONNEC: connected to upstream server, address: snix.ir:8990
2021/11/17 20:55:45 CONNEC: connected to upstream server, address: snix.ir:9898
2021/11/17 20:56:10 CONNEC: connected to upstream server, address: snix.ir:8990
2021/11/17 20:56:11 CONNEC: connected to upstream server, address: snix.ir:9898
2021/11/17 20:56:38 CONNEC: connected to upstream server, address: snix.ir:8990
2021/11/17 20:56:39 CONNEC: connected to upstream server, address: snix.ir:9898
...توسعه و پیشبرد goshkan: اگه دوست دارید توی توسعه goshkan مشارکت کنید میتونید به github.com/sina-ghaderi/goshkan مراجعه کنید و کارو از اونجا شروع کنید.. و در انتها امیدوارم از این پست لذت برده باشید.. اگه مشکلی توی نصب و پیاده سازی این سرویس دارید میتونید توی قسمت کامنت ها مطرح کنید، همچنین خوشحال میشم اگه پیشنهادات و انتقادات خودتون رو باهام به اشتراک بزارید..این مطلب در وبسایت blog.snix.ir منتشر شده است!</description>
                <category>سینا قادری</category>
                <author>سینا قادری</author>
                <pubDate>Thu, 18 Nov 2021 09:06:27 +0330</pubDate>
            </item>
            </channel>
</rss>