Move things around, set up a more structured app

This commit is contained in:
Cadey Ratio 2017-01-18 09:02:44 -08:00
parent bde3d7da90
commit f7df55f044
7 changed files with 1339 additions and 200 deletions

10
README.md Normal file
View File

@ -0,0 +1,10 @@
route
=====
A simple cluster-friendly load balancer for singleton microservices.
TODO
----
- [ ] Store stuff into RethinkDB
- [ ] Add Let's Encrypt support in addition to a free tor hidden service with every domain routed

80
database/db.go Normal file
View File

@ -0,0 +1,80 @@
package database
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"git.xeserv.us/xena/route/routerpc"
r "github.com/GoRethink/gorethink"
)
type DB struct {
s *r.Session
}
func New(host, database string) (*DB, error) {
session, err := r.Connect(r.ConnectOpts{
Address: host,
Database: database,
})
if err != nil {
return nil, err
}
db := &DB{
s: session,
}
return db, nil
}
var tables = []string{
"certs",
"routes",
}
type Route struct {
ID string `gorethink:"id,omitempty"`
Hostname string `gorethink:"hostname"`
OnionHostname string `gorethink:"onionhostname"`
Token string `gorethink:"token"`
OnionKey []byte `gorethink:"onionKey"` // PEM-encoded
}
func (db *DB) SaveRoute(resp *routerpc.AddHostResponse) error {
pemblock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(resp.PrivKey.(*rsa.PrivateKey)),
}
bytes := pem.EncodeToMemory(pemblock)
rt := &Route{
Hostname: resp.Hostname,
OnionHostname: resp.OnionHostname,
Token: resp.Token,
OnionKey: bytes,
}
_, err := r.Table("routes").Insert(rt).RunWrite(db.s)
if err != nil {
return err
}
return nil
}
func (db *DB) GetAllRoutes() ([]Route, error) {
rows, err := r.Table("routes").Run(db.s)
if err != nil {
return nil, err
}
var routes []Route
err = rows.All(&routes)
if err != nil {
return nil, err
}
return routes, nil
}

505
lib/elfs/elfs.go Normal file
View File

@ -0,0 +1,505 @@
/*
Package elfs is this project's heroku style name generator.
*/
package elfs
import (
"fmt"
"math/rand"
)
// Names is the name of every Pokemon from Pokemon Vietnamese Crystal.
var Names = []string{
"SEED",
"GRASS",
"FLOWE",
"SHAD",
"CABR",
"SNAKE",
"GOLD",
"COW",
"GUIKI",
"PEDAL",
"DELAN",
"B-FLY",
"BIDE",
"KEYU",
"FORK",
"LAP",
"PIGE",
"PIJIA",
"CAML",
"LAT",
"BIRD",
"BABOO",
"VIV",
"ABOKE",
"PIKAQ",
"RYE",
"SAN",
"BREAD",
"LIDEL",
"LIDE",
"PIP",
"PIKEX",
"ROK",
"JUGEN",
"PUD",
"BUDE",
"ZHIB",
"GELU",
"GRAS",
"FLOW",
"LAFUL",
"ATH",
"BALA",
"CORN",
"MOLUF",
"DESP",
"DAKED",
"MIMI",
"BOLUX",
"KODA",
"GELUD",
"MONK",
"SUMOY",
"GEDI",
"WENDI",
"NILEM",
"NILE",
"NILEC",
"KEZI",
"YONGL",
"HUDE",
"WANLI",
"GELI",
"GUAIL",
"MADAQ",
"WUCI",
"WUCI",
"MUJEF",
"JELLY",
"SICIB",
"GELU",
"NELUO",
"BOLI",
"JIALE",
"YED",
"YEDE",
"CLO",
"SCARE",
"AOCO",
"DEDE",
"DEDEI",
"BAWU",
"JIUG",
"BADEB",
"BADEB",
"HOLE",
"BALUX",
"GES",
"FANT",
"QUAR",
"YIHE",
"SWAB",
"SLIPP",
"CLU",
"DEPOS",
"BILIY",
"YUANO",
"SOME",
"NO",
"YELA",
"EMPT",
"ZECUN",
"XIAHE",
"BOLEL",
"DEJI",
"MACID",
"XIHON",
"XITO",
"LUCK",
"MENJI",
"GELU",
"DECI",
"XIDE",
"DASAJ",
"DONGN",
"RICUL",
"MINXI",
"BALIY",
"ZENDA",
"LUZEL",
"HELE5",
"0FENB",
"KAIL",
"JIAND",
"CARP",
"JINDE",
"LAPU",
"MUDE",
"YIFU",
"LINLI",
"SANDI",
"HUSI",
"JINC",
"OUMU",
"OUMUX",
"CAP",
"KUIZA",
"PUD",
"TIAO",
"FRMAN",
"CLAU",
"SPARK",
"DRAGO",
"BOLIU",
"GUAIL",
"MIYOU",
"MIY",
"QIAOK",
"BEIL",
"MUKEI",
"RIDED",
"MADAM",
"BAGEP",
"CROC",
"ALIGE",
"OUDAL",
"OUD",
"DADA",
"HEHE",
"YEDEA",
"NUXI",
"NUXIN",
"ROUY",
"ALIAD",
"STICK",
"QIANG",
"LAAND",
"PIQI",
"PI",
"PUPI",
"DEKE",
"DEKEJ",
"NADI",
"NADIO",
"MALI",
"PEA",
"ELECT",
"FLOWE",
"MAL",
"MALI",
"HUSHU",
"NILEE",
"YUZI",
"POPOZ",
"DUZI",
"HEBA",
"XIAN",
"SHAN",
"YEYEA",
"WUY",
"LUO",
"KEFE",
"HULA",
"CROW",
"YADEH",
"MOW",
"ANNAN",
"SUONI",
"KYLI",
"HULU",
"HUDEL",
"YEHE",
"GULAE",
"YEHE",
"BLU",
"GELAN",
"BOAT",
"NIP",
"POIT",
"HELAK",
"XINL",
"BEAR",
"LINB",
"MAGEH",
"MAGEJ",
"WULI",
"YIDE",
"RIVE",
"FISH",
"AOGU",
"DELIE",
"MANTE",
"KONMU",
"DELU",
"HELU",
"HUAN",
"HUMA",
"DONGF",
"JINCA",
"HEDE",
"DEFU",
"LIBY",
"JIAPA",
"MEJI",
"HELE",
"BUHU",
"MILK",
"HABI",
"THUN",
"GARD",
"DON",
"YANGQ",
"SANAQ",
"BANQ",
"LUJ",
"PHIX",
"SIEI",
"EGG",
}
// Moves is every single move from Pokemon Vietnamese Crystal.
var Moves = []string{
"ABLE",
"ABNORMA",
"AGAIN",
"AIREXPL",
"ANG",
"ANGER",
"ASAIL",
"ATTACK",
"AURORA",
"AWL",
"BAN",
"BAND",
"BARE",
"BEAT",
"BEATED",
"BELLY",
"BIND",
"BITE",
"BLOC",
"BLOOD",
"BODY",
"BOOK",
"BREATH",
"BUMP",
"CAST",
"CHAM",
"CLAMP",
"CLAP",
"CLAW",
"CLEAR",
"CLI",
"CLIP",
"CLOUD",
"CONTRO",
"CONVY",
"COOLHIT",
"CRASH",
"CRY",
"CUT",
"DESCRI",
"D-FIGHT",
"DIG",
"DITCH",
"DIV",
"DOZ",
"DRE",
"DUL",
"DU-PIN",
"DYE",
"EARTH",
"EDU",
"EG-BOMB",
"EGG",
"ELEGY",
"ELE-HIT",
"EMBODY",
"EMPLI",
"ENGL",
"ERUPT",
"EVENS",
"EXPLOR",
"EYES",
"FALL",
"FAST",
"F-CAR",
"F-DANCE",
"FEARS",
"F-FIGHT",
"FIGHT",
"FIR",
"FIRE",
"FIREHIT",
"FLAME",
"FLAP",
"FLASH",
"FLEW",
"FORCE",
"FRA",
"FREEZE",
"FROG",
"G-BIRD",
"GENKISS",
"GIFT",
"G-KISS",
"G-MOUSE",
"GRADE",
"GROW",
"HAMMER",
"HARD",
"HAT",
"HATE",
"H-BOMB",
"HELL-R",
"HEMP",
"HINT",
"HIT",
"HU",
"HUNT",
"HYPNOSI",
"INHA",
"IRO",
"IRONBAR",
"IR-WING",
"J-GUN",
"KEE",
"KICK",
"KNIF",
"KNIFE",
"KNOCK",
"LEVEL",
"LIGH",
"LIGHHIT",
"LIGHT",
"LIVE",
"L-WALL",
"MAD",
"MAJUS",
"MEL",
"MELO",
"MESS",
"MILK",
"MIMI",
"MISS",
"MIXING",
"MOVE",
"MUD",
"NI-BED",
"NOISY",
"NOONLI",
"NULL",
"N-WAVE",
"PAT",
"PEACE",
"PIN",
"PLAN",
"PLANE",
"POIS",
"POL",
"POWDE",
"POWE",
"POWER",
"PRIZE",
"PROTECT",
"PROUD",
"RAGE",
"RECOR",
"REFLAC",
"REFREC",
"REGR",
"RELIV",
"RENEW",
"R-FIGHT",
"RING",
"RKICK",
"ROCK",
"ROUND",
"RUS",
"RUSH",
"SAND",
"SAW",
"SCISSOR",
"SCRA",
"SCRIPT",
"SEEN",
"SERVER",
"SHADOW",
"SHELL",
"SHINE",
"SHO",
"SIGHT",
"SIN",
"SMALL",
"SMELT",
"SMOK",
"SNAKE",
"SNO",
"SNOW",
"SOU",
"SO-WAVE",
"SPAR",
"SPEC",
"SPID",
"S-PIN",
"SPRA",
"STAM",
"STARE",
"STEA",
"STONE",
"STORM",
"STRU",
"STRUG",
"STUDEN",
"SUBS",
"SUCID",
"SUN-LIG",
"SUNRIS",
"SUPLY",
"S-WAVE",
"TAILS",
"TANGL",
"TASTE",
"TELLI",
"THANK",
"TONKICK",
"TOOTH",
"TORL",
"TRAIN",
"TRIKICK",
"TUNGE",
"VOLT",
"WA-GUN",
"WATCH",
"WAVE",
"W-BOMB",
"WFALL",
"WFING",
"WHIP",
"WHIRL",
"WIND",
"WOLF",
"WOOD",
"WOR",
"YUJA",
}
func randomMove() string {
return Moves[rand.Intn(len(Moves))]
}
func randomName() string {
return Names[rand.Intn(len(Names))]
}
// MakeName generates a new domain name based on the moves and Pokemon names
// from Pokemon Vietnamese Crystal.
func MakeName() string {
move1 := randomMove()
move2 := randomMove()
poke := randomName()
return fmt.Sprintf("%s-%s-%s", move1, move2, poke)
}

225
lib/elfs/moves.moon Normal file
View File

@ -0,0 +1,225 @@
{
"ABLE",
"ABNORMA",
"AGAIN",
"AIREXPL",
"ANG",
"ANGER",
"ASAIL",
"ATTACK",
"AURORA",
"AWL",
"BAN",
"BAND",
"BARE",
"BEAT",
"BEATED",
"BELLY",
"BIND",
"BITE",
"BLOC",
"BLOOD",
"BODY",
"BOOK",
"BREATH",
"BUMP",
"CAST",
"CHAM",
"CLAMP",
"CLAP",
"CLAW",
"CLEAR",
"CLI",
"CLIP",
"CLOUD",
"CONTRO",
"CONVY",
"COOLHIT",
"CRASH",
"CRY",
"CUT",
"DESCRI",
"D-FIGHT",
"DIG",
"DITCH",
"DIV",
"DOZ",
"DRE",
"DUL",
"DU-PIN",
"DYE",
"EARTH",
"EDU",
"EG-BOMB",
"EGG",
"ELEGY",
"ELE-HIT",
"EMBODY",
"EMPLI",
"ENGL",
"ERUPT",
"EVENS",
"EXPLOR",
"EYES",
"FALL",
"FAST",
"F-CAR",
"F-DANCE",
"FEARS",
"F-FIGHT",
"FIGHT",
"FIR",
"FIRE",
"FIREHIT",
"FLAME",
"FLAP",
"FLASH",
"FLEW",
"FORCE",
"FRA",
"FREEZE",
"FROG",
"G-BIRD",
"GENKISS",
"GIFT",
"G-KISS",
"G-MOUSE",
"GRADE",
"GROW",
"HAMMER",
"HARD",
"HAT",
"HATE",
"H-BOMB",
"HELL-R",
"HEMP",
"HINT",
"HIT",
"HU",
"HUNT",
"HYPNOSI",
"INHA",
"IRO",
"IRONBAR",
"IR-WING",
"J-GUN",
"KEE",
"KICK",
"KNIF",
"KNIFE",
"KNOCK",
"LEVEL",
"LIGH",
"LIGHHIT",
"LIGHT",
"LIVE",
"L-WALL",
"MAD",
"MAJUS",
"MEL",
"MELO",
"MESS",
"MILK",
"MIMI",
"MISS",
"MIXING",
"MOVE",
"MUD",
"NI-BED",
"NOISY",
"NOONLI",
"NULL",
"N-WAVE",
"PAT",
"PEACE",
"PIN",
"PLAN",
"PLANE",
"POIS",
"POL",
"POWDE",
"POWE",
"POWER",
"PRIZE",
"PROTECT",
"PROUD",
"RAGE",
"RECOR",
"REFLAC",
"REFREC",
"REGR",
"RELIV",
"RENEW",
"R-FIGHT",
"RING",
"RKICK",
"ROCK",
"ROUND",
"RUS",
"RUSH",
"SAND",
"SAW",
"SCISSOR",
"SCRA",
"SCRIPT",
"SEEN",
"SERVER",
"SHADOW",
"SHELL",
"SHINE",
"SHO",
"SIGHT",
"SIN",
"SMALL",
"SMELT",
"SMOK",
"SNAKE",
"SNO",
"SNOW",
"SOU",
"SO-WAVE",
"SPAR",
"SPEC",
"SPID",
"S-PIN",
"SPRA",
"STAM",
"STARE",
"STEA",
"STONE",
"STORM",
"STRU",
"STRUG",
"STUDEN",
"SUBS",
"SUCID",
"SUN-LIG",
"SUNRIS",
"SUPLY",
"S-WAVE",
"TAILS",
"TANGL",
"TASTE",
"TELLI",
"THANK",
"TONKICK",
"TOOTH",
"TORL",
"TRAIN",
"TRIKICK",
"TUNGE",
"VOLT",
"WA-GUN",
"WATCH",
"WAVE",
"W-BOMB",
"WFALL",
"WFING",
"WHIP",
"WHIRL",
"WIND",
"WOLF",
"WOOD",
"WOR",
"YUJA",
}

250
lib/elfs/names.moon Normal file
View File

@ -0,0 +1,250 @@
{
"SEED",
"GRASS",
"FLOWE",
"SHAD",
"CABR",
"SNAKE",
"GOLD",
"COW",
"GUIKI",
"PEDAL",
"DELAN",
"B-FLY",
"BIDE",
"KEYU",
"FORK",
"LAP",
"PIGE",
"PIJIA",
"CAML",
"LAT",
"BIRD",
"BABOO",
"VIV",
"ABOKE",
"PIKAQ",
"RYE",
"SAN",
"BREAD",
"LIDEL",
"LIDE",
"PIP",
"PIKEX",
"ROK",
"JUGEN",
"PUD",
"BUDE",
"ZHIB",
"GELU",
"GRAS",
"FLOW",
"LAFUL",
"ATH",
"BALA",
"CORN",
"MOLUF",
"DESP",
"DAKED",
"MIMI",
"BOLUX",
"KODA",
"GELUD",
"MONK",
"SUMOY",
"GEDI",
"WENDI",
"NILEM",
"NILE",
"NILEC",
"KEZI",
"YONGL",
"HUDE",
"WANLI",
"GELI",
"GUAIL",
"MADAQ",
"WUCI",
"WUCI",
"MUJEF",
"JELLY",
"SICIB",
"GELU",
"NELUO",
"BOLI",
"JIALE",
"YED",
"YEDE",
"CLO",
"SCARE",
"AOCO",
"DEDE",
"DEDEI",
"BAWU",
"JIUG",
"BADEB",
"BADEB",
"HOLE",
"BALUX",
"GES",
"FANT",
"QUAR",
"YIHE",
"SWAB",
"SLIPP",
"CLU",
"DEPOS",
"BILIY",
"YUANO",
"SOME",
"NO",
"YELA",
"EMPT",
"ZECUN",
"XIAHE",
"BOLEL",
"DEJI",
"MACID",
"XIHON",
"XITO",
"LUCK",
"MENJI",
"GELU",
"DECI",
"XIDE",
"DASAJ",
"DONGN",
"RICUL",
"MINXI",
"BALIY",
"ZENDA",
"LUZEL",
"HELE5",
"0FENB",
"KAIL",
"JIAND",
"CARP",
"JINDE",
"LAPU",
"MUDE",
"YIFU",
"LINLI",
"SANDI",
"HUSI",
"JINC",
"OUMU",
"OUMUX",
"CAP",
"KUIZA",
"PUD",
"TIAO",
"FRMAN",
"CLAU",
"SPARK",
"DRAGO",
"BOLIU",
"GUAIL",
"MIYOU",
"MIY",
"QIAOK",
"BEIL",
"MUKEI",
"RIDED",
"MADAM",
"BAGEP",
"CROC",
"ALIGE",
"OUDAL",
"OUD",
"DADA",
"HEHE",
"YEDEA",
"NUXI",
"NUXIN",
"ROUY",
"ALIAD",
"STICK",
"QIANG",
"LAAND",
"PIQI",
"PI",
"PUPI",
"DEKE",
"DEKEJ",
"NADI",
"NADIO",
"MALI",
"PEA",
"ELECT",
"FLOWE",
"MAL",
"MALI",
"HUSHU",
"NILEE",
"YUZI",
"POPOZ",
"DUZI",
"HEBA",
"XIAN",
"SHAN",
"YEYEA",
"WUY",
"LUO",
"KEFE",
"HULA",
"CROW",
"YADEH",
"MOW",
"ANNAN",
"SUONI",
"KYLI",
"HULU",
"HUDEL",
"YEHE",
"GULAE",
"YEHE",
"BLU",
"GELAN",
"BOAT",
"NIP",
"POIT",
"HELAK",
"XINL",
"BEAR",
"LINB",
"MAGEH",
"MAGEJ",
"WULI",
"YIDE",
"RIVE",
"FISH",
"AOGU",
"DELIE",
"MANTE",
"KONMU",
"DELU",
"HELU",
"HUAN",
"HUMA",
"DONGF",
"JINCA",
"HEDE",
"DEFU",
"LIBY",
"JIAPA",
"MEJI",
"HELE",
"BUHU",
"MILK",
"HABI",
"THUN",
"GARD",
"DON",
"YANGQ",
"SANAQ",
"BANQ",
"LUJ",
"PHIX",
"SIEI",
"EGG",
}

206
main.go
View File

@ -1,30 +1,17 @@
package main
import (
"crypto/x509"
"encoding/pem"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"net/rpc"
"os"
"path/filepath"
"strconv"
"time"
"git.xeserv.us/xena/route/routerpc"
r "github.com/GoRethink/gorethink"
"github.com/Xe/uuid"
"github.com/Yawning/bulb"
"git.xeserv.us/xena/route/server"
"github.com/facebookgo/flagenv"
_ "github.com/joho/godotenv/autoload"
"github.com/koding/tunnel"
"github.com/sycamoreone/orc/tor"
)
var (
@ -36,11 +23,7 @@ var (
torHashedPassword = flag.String("tor-hashed-password", "", "Tor hashed password")
torPassword = flag.String("tor-password", "hunter2", "Tor clear password")
webPort = flag.String("web-port", "9234", "HTTP ingress port for backends and users")
)
// RPC constants
const (
RPCPort uint16 = 39453
domainSuffix = flag.String("domain-suffix", ".apps.xeserv.us", "Domain name suffix associated with the load balancer")
)
func main() {
@ -48,7 +31,7 @@ func main() {
flagenv.Parse()
rand.Seed(time.Now().Unix())
s, err := create(&ServerConfig{
s, err := server.New(&server.ServerConfig{
ControlHost: *controlHost,
ControlKeyFile: *controlKeyFile,
RethinkDBHost: *rethinkDBHost,
@ -56,6 +39,8 @@ func main() {
TorDataDir: *torDataDir,
TorHashedPassword: *torHashedPassword,
TorPassword: *torPassword,
WebPort: *webPort,
DomainSuffix: *domainSuffix,
})
if err != nil {
log.Fatal(err)
@ -74,182 +59,3 @@ func main() {
hs.Serve(l)
}
// Server is the main server type
type Server struct {
cfg *ServerConfig
sess *r.Session
torCon *bulb.Conn
torCmd *tor.Cmd
torControlPath string
rpcS *rpc.Server
rpcAddr string
ts *tunnel.Server
}
// ServerConfig configures Server
type ServerConfig struct {
ControlHost, ControlKeyFile string
RethinkDBHost, RethinkDBDatabase string
TorDataDir, TorHashedPassword, TorPassword string
}
func create(cfg *ServerConfig) (*Server, error) {
session, err := r.Connect(r.ConnectOpts{
Address: cfg.RethinkDBHost,
Database: cfg.RethinkDBDatabase,
})
if err != nil {
return nil, err
}
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63()))
tc := tor.NewConfig()
tc.Set("DataDirectory", cfg.TorDataDir)
tc.Set("HashedControlPassword", cfg.TorHashedPassword)
tc.Set("SocksPort", "0")
cp := rand.Intn(64512) // 64k - 1k
tc.Set("ControlPort", cp)
tc.Timeout = 30 * time.Second
log.Println(tc.ToCmdLineFormat())
tcmd, err := tor.NewCmd(tc)
if err != nil {
return nil, err
}
err = tcmd.Start()
if err != nil {
return nil, err
}
time.Sleep(5 * time.Second)
bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp))
if err != nil {
return nil, err
}
err = bc.Authenticate(cfg.TorPassword)
if err != nil {
return nil, err
}
fin, err := os.Open(cfg.ControlKeyFile)
if err != nil {
return nil, err
}
defer fin.Close()
data, err := ioutil.ReadAll(fin)
if err != nil {
return nil, err
}
var block *pem.Block
block, _ = pem.Decode([]byte(data))
pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
_, err = bc.AddOnion([]bulb.OnionPortSpec{
bulb.OnionPortSpec{
VirtPort: RPCPort,
Target: l.Addr().String(),
},
}, pKey, false)
if err != nil {
return nil, err
}
rpcs := rpc.NewServer()
ts, err := tunnel.NewServer(&tunnel.ServerConfig{})
if err != nil {
return nil, err
}
s := &Server{
cfg: cfg,
sess: session,
torCon: bc,
torCmd: tcmd,
torControlPath: torControlPath,
rpcS: rpcs,
rpcAddr: l.Addr().String(),
ts: ts,
}
rpcs.RegisterName("Urls", &RPCServer{Server: s})
go rpcs.Accept(l)
return s, nil
}
func (s *Server) onionPath(name string) string {
return filepath.Join(s.cfg.TorDataDir, name)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == rpc.DefaultRPCPath {
s.rpcS.ServeHTTP(w, r)
return
}
s.ts.ServeHTTP(w, r)
}
type RPCServer struct {
*Server
}
func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error {
if req.APIKey != "hunter2" {
return errors.New("invalid api key")
}
token := uuid.New()
oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{
bulb.OnionPortSpec{
VirtPort: 80,
Target: "127.0.0.1:" + *webPort,
},
}, req.PrivKey, false)
if err != nil {
return err
}
resp.OnionHostname = oi.OnionID + ".onion"
resp.Token = token
if req.Hostname != "" {
rs.Server.ts.AddHost(req.Hostname, token)
resp.Hostname = req.Hostname
}
rs.Server.ts.AddHost(resp.OnionHostname, token)
if oi.PrivateKey != nil {
resp.PrivKey = oi.PrivateKey
} else {
resp.PrivKey = req.PrivKey
}
return nil
}

263
server/server.go Normal file
View File

@ -0,0 +1,263 @@
package server
import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
"net"
"net/http"
"net/rpc"
"os"
"path/filepath"
"strconv"
"time"
"git.xeserv.us/xena/route/database"
"git.xeserv.us/xena/route/lib/elfs"
"git.xeserv.us/xena/route/routerpc"
"github.com/Xe/uuid"
"github.com/Yawning/bulb"
"github.com/koding/tunnel"
"github.com/sycamoreone/orc/tor"
)
// RPC constants
const (
RPCPort uint16 = 39453
)
// Server is the main server type
type Server struct {
cfg *ServerConfig
db *database.DB
torCon *bulb.Conn
torCmd *tor.Cmd
torControlPath string
rpcS *rpc.Server
rpcAddr string
ts *tunnel.Server
}
// ServerConfig configures Server
type ServerConfig struct {
ControlHost, ControlKeyFile string
RethinkDBHost, RethinkDBDatabase string
TorDataDir, TorHashedPassword, TorPassword string
WebPort, DomainSuffix string
}
// New creates a new Server
func New(cfg *ServerConfig) (*Server, error) {
db, err := database.New(cfg.RethinkDBHost, cfg.RethinkDBDatabase)
if err != nil {
return nil, err
}
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63()))
tc := tor.NewConfig()
tc.Set("DataDirectory", cfg.TorDataDir)
tc.Set("HashedControlPassword", cfg.TorHashedPassword)
tc.Set("SocksPort", "0")
cp := rand.Intn(64512) // 64k - 1k
tc.Set("ControlPort", cp)
tc.Timeout = 30 * time.Second
log.Println(tc.ToCmdLineFormat())
tcmd, err := tor.NewCmd(tc)
if err != nil {
return nil, err
}
err = tcmd.Start()
if err != nil {
return nil, err
}
time.Sleep(5 * time.Second)
bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp))
if err != nil {
return nil, err
}
err = bc.Authenticate(cfg.TorPassword)
if err != nil {
return nil, err
}
fin, err := os.Open(cfg.ControlKeyFile)
if err != nil {
return nil, err
}
defer fin.Close()
data, err := ioutil.ReadAll(fin)
if err != nil {
return nil, err
}
var block *pem.Block
block, _ = pem.Decode([]byte(data))
pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
_, err = bc.AddOnion([]bulb.OnionPortSpec{
bulb.OnionPortSpec{
VirtPort: RPCPort,
Target: l.Addr().String(),
},
}, pKey, false)
if err != nil {
return nil, err
}
rpcs := rpc.NewServer()
ts, err := tunnel.NewServer(&tunnel.ServerConfig{})
if err != nil {
return nil, err
}
s := &Server{
cfg: cfg,
db: db,
torCon: bc,
torCmd: tcmd,
torControlPath: torControlPath,
rpcS: rpcs,
rpcAddr: l.Addr().String(),
ts: ts,
}
rpcs.RegisterName("Urls", &RPCServer{Server: s})
go rpcs.Accept(l)
err = s.restore()
if err != nil {
return nil, err
}
return s, nil
}
func (s *Server) onionPath(name string) string {
return filepath.Join(s.cfg.TorDataDir, name)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == rpc.DefaultRPCPath {
s.rpcS.ServeHTTP(w, r)
return
}
s.ts.ServeHTTP(w, r)
}
func (s *Server) restore() error {
rts, err := s.db.GetAllRoutes()
if err != nil {
return err
}
for _, rt := range rts {
var block *pem.Block
block, _ = pem.Decode([]byte(rt.OnionKey))
pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
ports := []bulb.OnionPortSpec{
genPortSpec(80, "127.0.0.1:"+s.cfg.WebPort),
}
_, err = s.torCon.AddOnion(ports, pKey, false)
if err != nil {
return err
}
s.ts.AddHost(rt.Hostname, rt.Token)
s.ts.AddHost(rt.OnionHostname, rt.Token)
log.Printf("added: %s (%s)", rt.Hostname, rt.OnionHostname)
}
return nil
}
func genPortSpec(incoming uint16, outgoing string) bulb.OnionPortSpec {
return bulb.OnionPortSpec{
VirtPort: incoming,
Target: outgoing,
}
}
// RPCServer is a Server wrapped to work inside the context of net/rpc
type RPCServer struct {
*Server
}
func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error {
if req.APIKey != "hunter2" {
return errors.New("invalid api key")
}
token := uuid.New()
oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{
bulb.OnionPortSpec{
VirtPort: 80,
Target: "127.0.0.1:" + rs.cfg.WebPort,
},
}, req.PrivKey, false)
if err != nil {
return err
}
resp.OnionHostname = oi.OnionID + ".onion"
resp.Token = token
if req.Hostname != "" {
rs.Server.ts.AddHost(req.Hostname, token)
resp.Hostname = req.Hostname
} else {
resp.Hostname = elfs.MakeName() + rs.cfg.DomainSuffix
rs.ts.AddHost(resp.Hostname, token)
}
rs.Server.ts.AddHost(resp.OnionHostname, token)
if oi.PrivateKey != nil {
resp.PrivKey = oi.PrivateKey
} else {
resp.PrivKey = req.PrivKey
}
err = rs.db.SaveRoute(resp)
if err != nil {
return err
}
return nil
}