سینا قادری
سینا قادری
خواندن ۱۴ دقیقه·۳ سال پیش

پیاده سازی پروکسی goshkan مشابه سایت شکن نوشته شده با گولنگ

خب قبل از هرچیز، احتمالا با شکن (سایت شکن) اشنا هستید، شکن عملا یک سرور 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 ( &quotnet&quot ) func main() { l, err := net.Listen(&quottcp&quot, &quot127.0.0.1:7854&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(&quothello world&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 لاین 129

func (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}, &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 لاین 44

func networkOpt(conn net.Conn) (*sockOpt, error) { var nftab = newNfTab() fd, err := conn.(*net.TCPConn).File() 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])<<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 && chmod u+x goshkan # mkdir -p /opt/goshkan/ && mv goshkan /opt/goshkan/ # > server-config.json

خب فایل باینری (اجرایی) رو دانلود کردیم و دایرکتوری های مورد نظر رو ساختیم! الان موقع کانفیگ هست.. فایل کانفیگ بر مبنای json هست و اپشن های در دسترس اینها هستن..

{ &quotMYSQL_PASSWORD&quot: &quotpassword&quot, &quotMYSQL_USERNAME&quot: &quotusername&quot, &quotDOMAIN_MEMTTL&quot: 60, &quotMYSQL_DATABASE&quot: &quotgoshkan&quot, &quotMYSQL_ADDRESS&quot: &quotlocalhost&quot, &quotCONNECT_TIMEOUT&quot: 10, &quotLISTEN_ADDRESS&quot: &quot192.168.122.149:8443&quot, &quotCLIENT_TIMEOUT&quot: 15, &quotHTTPAPI_LISTEN&quot: &quot127.0.0.1:8080&quot, &quotLOGS_DEBUGGING&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 بزازید و محتویات زیر رو توش کپی کنید

# > 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 'username'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON goshkan.* TO 'username'@'localhost'; 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: &quotgoproxy&quot access-control-tag: 0.0.0.0/0 &quotgoproxy&quot ## pass 192.168.122.149 (goshkan proxy) for goproxy tag domains. access-control-tag-data: 0.0.0.0/0 &quotgoproxy&quot &quotA 192.168.122.149&quot ####### Domains List Goes Here ######## ## new local-zone for snix.ir that should be redirected to goproxy local-zone: &quotsnix.ir&quot redirect local-zone-tag: &quotsnix.ir&quot &quotgoproxy&quot local-zone: &quotkernel.org&quot redirect local-zone-tag: &quotkernel.org&quot &quotgoproxy&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 '{&quotRegex&quot:&quotsnix.ir&quot}' -X POST localhost:8080/api/add/ # curl -X GET localhost:8080/api/all/ [ { &quotRegex&quot: &quotsnix.ir&quot, &quotRegID&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%[=================================================================================================>] 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 منتشر شده است!

golangnetworkproxylinuxnetfilter
توسعه دهنده و مهندس شبکه
شاید از این پست‌ها خوشتان بیاید