func (cc *ClientCtx) thrMain() { defer cc.Close() // thrMain only handles incoming segments for { select { case <-cc.cmn.deadch: return case seg := <-cc.cmn.dnch: switch seg.Flag { case kcOPEN: kilog.Warning("kiricom: severe protocol violation: kcOPEN on client") return case kcDATA: fallthrough case kcCLOS: cc.tablock.Lock() tgt, ok := cc.conntab[seg.ConnID] cc.tablock.Unlock() if !ok { kilog.FineDebug("kiricom: %v to invalid connid", seg) continue } select { case tgt.inbox <- seg: default: kilog.Warning("kiricom: severe protocol violation: overfull flow control buffer") return } if seg.Flag == kcCLOS { cc.tablock.Lock() delete(cc.conntab, seg.ConnID) cc.tablock.Unlock() } case kcMORE: cc.tablock.Lock() tgt, ok := cc.conntab[seg.ConnID] cc.tablock.Unlock() if !ok { kilog.FineDebug("kiricom: %v to invalid connid", seg) continue } select { case tgt.morech <- true: kilog.FineDebug("kiricom: sent MORE to connid %v", tgt.connid) default: kilog.Warning("kiricom: severe protocol violation: duplicate MORE") return } } } } }
// LLObfsServerHandshake negotiates low-level obfuscation (content hiding but no // volume hiding) on a network connection, acting as the server. The master // secret must be provided. func LLObfsServerHandshake(secret []byte, transport io.ReadWriteCloser) (io.ReadWriteCloser, error) { // Client needs to send proof that they actually have our secret proof := make([]byte, 64) _, err := io.ReadFull(transport, proof) if err != nil { return nil, err } kilog.FineDebug("llobfs: server obtained proof") // We need to verify proof nonce := proof[:32] hash := proof[32:] if subtle.ConstantTimeCompare(natrium.SecureHash(secret, nonce), hash) != 1 { return nil, errors.New("Client did not give the right proof") } // Generate our ephemeral keys our_keys := UDHGenerateKeys() // Send our public key _, err = transport.Write(our_keys.Public) if err != nil { return nil, err } kilog.FineDebug("llobfs: server sent public key") // Read their public key their_public := make([]byte, 1536/8) _, err = io.ReadFull(transport, their_public) if err != nil { return nil, err } kilog.FineDebug("llobfs: server read public key") // Compute shared secret shared_secret := UDHSecret(our_keys.Private, their_public) // Read and write keys read_key := natrium.SecureHash(shared_secret, []byte("llobfs-upstream-key")) write_key := natrium.SecureHash(shared_secret, []byte("llobfs-downstream-key")) // Create struct toret := new(llobfsContext) toret.read_chug, _ = rc4.NewCipher(read_key) toret.write_chug, _ = rc4.NewCipher(write_key) dummy := make([]byte, 1536) toret.read_chug.XORKeyStream(dummy, dummy) toret.write_chug.XORKeyStream(dummy, dummy) toret.underlying = transport return toret, nil }
func (cc *commonCtx) thrProcessDown() { for { var seg kcSegment err := struc.Unpack(cc.carrier, &seg) if err != nil { cc.destroy(err) return } // if we get ping, respond with a pong if seg.Flag == kcPING { kilog.FineDebug("kiricom: commonCtx successfully received ping %v", seg) seg.Flag = kcPONG go func() { select { case cc.upch <- seg: case <-cc.deadch: } }() continue } // if we get pong, update latency value if seg.Flag == kcPONG { lat := (time.Now().UnixNano() - int64(binary.BigEndian.Uint64(seg.Body))) / (1000 * 1000) kilog.FineDebug("kiricom: commonCtx successfully received pong (%v ms)", lat) cc.latency = int(lat) continue } select { case cc.dnch <- seg: kilog.FineDebug("kiricom: commonCtx successfully received %v", seg) select { case cc.wdch <- true: case <-cc.deadch: return } case <-cc.deadch: return case <-time.After(time.Second): panic("this should NEVER happen because dnch should always be read from") } } }
// LLObfsClientHandshake negotiates low-level obfuscation as a client. The server // secret must be given so that the client can prove knowledge. func LLObfsClientHandshake(secret []byte, transport io.ReadWriteCloser) (io.ReadWriteCloser, error) { // Prove knowledge to client first nonce := make([]byte, 32) rand.Read(nonce) hash := natrium.SecureHash(secret, nonce) _, err := transport.Write(append(nonce, hash...)) if err != nil { return nil, err } kilog.FineDebug("llobfs: client sent proof") // Read their public key their_public := make([]byte, 1536/8) _, err = io.ReadFull(transport, their_public) if err != nil { return nil, err } kilog.FineDebug("llobfs: client read server's public key") // Make our keys our_keys := UDHGenerateKeys() // Send our public key _, err = transport.Write(our_keys.Public) if err != nil { return nil, err } kilog.FineDebug("llobfs: client sent public key") // Compute shared secret shared_secret := UDHSecret(our_keys.Private, their_public) // Derive keys read_key := natrium.SecureHash(shared_secret, []byte("llobfs-downstream-key")) write_key := natrium.SecureHash(shared_secret, []byte("llobfs-upstream-key")) toret := new(llobfsContext) toret.read_chug, _ = rc4.NewCipher(read_key) toret.write_chug, _ = rc4.NewCipher(write_key) dummy := make([]byte, 1536) toret.read_chug.XORKeyStream(dummy, dummy) toret.write_chug.XORKeyStream(dummy, dummy) toret.underlying = transport return toret, nil }
// Push enqueues a segment into the jitter buffer. If the buffer // already contains `maxCount` or more segments, the new segment is // dropped and an error is returned. It's a bad idea to die on an error // returned from this function; it's usually safe to just ignore the // return value. func (jb *JitterBuffer) Push(seg Segment, maxCount int) (err error) { jb.cvar.L.Lock() defer jb.cvar.L.Unlock() defer jb.cvar.Broadcast() jb.hdLim = maxCount if len(jb.queue) >= maxCount { return errors.New("JitterBuffer overfull") } // calculate the correct sequence number expect8b := jb.expect % 256 queuepos := uint64(seg.Sequence) if queuepos >= expect8b && queuepos-expect8b < 128 { // [ 0 e n 0] queuepos += jb.expect - expect8b } else if queuepos < expect8b && queuepos+256-expect8b < 128 { // [ e 0 n 0 ] queuepos += 256 + jb.expect - expect8b } else { // drop packet kilog.FineDebug("trtp: JitterBuffer dropped packet (ex=%v, e8=%v, qp=%v)", jb.expect, queuepos, queuepos) return } // return if not too late if queuepos >= jb.expect { // max seqnum if queuepos > jb.topseq { jb.topseq = queuepos } defer kilog.FineDebug("trtp: JitterBuffer successfully enqueued packet %v (%v)", queuepos, seg.Sequence) jb.queue[queuepos] = seg } return }
func (cc *commonCtx) thrProcessUp() { for { select { case seg := <-cc.upch: err := struc.Pack(cc.carrier, &seg) if err != nil { cc.destroy(err) return } kilog.FineDebug("kiricom: commonCtx successfully transmitted %v", seg) case <-cc.deadch: return } } }
func (cc *ClientCtx) Dial() (io.ReadWriteCloser, error) { sok := newKcPayload(cc.limit, cc.cmn.upch) select { case <-cc.cmn.deadch: return nil, io.ErrClosedPipe case <-time.After(time.Second * 30): cc.Close() return nil, io.ErrNoProgress case num := <-cc.idpool: sok.connid = num kilog.FineDebug("kiricom: assigned number %v", num) opener := kcSegment{ Flag: kcOPEN, ConnID: num, } cc.tablock.Lock() cc.conntab[num] = sok cc.tablock.Unlock() // on death return the number go func() { <-sok.deadch cc.idpool <- num }() // on global death, die too go func() { // death coupler select { case <-sok.deadch: case <-cc.cmn.deadch: sok.destroy("global death") } }() select { case <-cc.cmn.deadch: return nil, io.ErrClosedPipe case <-time.After(time.Second * 30): return nil, io.ErrNoProgress case cc.cmn.upch <- opener: return sok, nil } } }
// HLObfsServerHandshake creates a fully obfuscating tunnel over any network connection. // Note that you have to close the resulting object properly; otherwise memory leaks // may result! This is the server version. func HLObfsServerHandshake(secret []byte, underlying io.ReadWriteCloser) (io.ReadWriteCloser, error) { kilog.FineDebug("hlobfs: server handshake called") realconn, err := LLObfsServerHandshake(secret, underlying) if err != nil { return nil, err } toret := new(hlobfsContext) toret.randdist = makeProbDistro() toret.stop_ch = make(chan int, 256) toret.underlying = realconn toret.write_lk = make(chan int, 1) toret.write_lk <- 0 toret.read_buff = nil toret.readclosed = false go hlobfsBackgroundGbg(toret) return toret, nil }
// EasyListen listens for incoming kiricom connections. // // If privkey is nil, then no KiSS would be done, just obfuscation. func EasyListen(addr string, privkey natrium.EdDSAPrivate, secret []byte) (toret *EasyListener, err error) { tcpadd, err := net.ResolveTCPAddr("tcp", addr) if err != nil { return } toret = new(EasyListener) toret.Addr = tcpadd toret.Secret = secret toret.core, err = net.ListenTCP("tcp", tcpadd) if err != nil { return } toret.deadch = make(chan bool) toret.sockch = make(chan io.ReadWriteCloser) go func() { for { lol, err := toret.core.Accept() if err != nil { return } lol.(*net.TCPConn).SetNoDelay(true) go func() { obfs, err := kiss.LLObfsServerHandshake(secret, lol) if err != nil { kilog.FineDebug("kiricom: EasyListen failed HLObfs: %v", err) lol.Close() return } if privkey != nil { kss, err := kiss.KiSSNamedHandshake(privkey, nil, obfs) if err != nil { kilog.FineDebug("kiricom: EasyListen failed KiSS: %v", kss) obfs.Close() return } obfs = kss } state := NewServerCtx(32768, obfs) go func() { <-toret.deadch state.Close() }() defer state.Close() for { sok, err := state.Accept() if err != nil { kilog.Debug("kiricom: EasyListen internal accept error %v", err.Error()) return } select { case toret.sockch <- sok: case <-toret.deadch: return } } }() } }() return toret, nil }
func run_icom_ctx(ctx *icom_ctx, KILL func(), is_server bool, do_junk bool, PAUSELIM int) { defer KILL() socket_table := make([]chan icom_msg, 65536) stable_lock := make(chan bool, 1) stable_lock <- true prob_dist := MakeProbDistro() junk_chan := make(chan bool) // Write junk echo packets to mask webpage loading if do_junk { go func() { defer KILL() for { desired_size := prob_dist.Draw() * 2 select { case <-ctx.killswitch: return case <-junk_chan: select { case <-ctx.killswitch: case ctx.write_ch <- icom_msg{icom_ignore, 0, make([]byte, desired_size)}: default: } } } }() } // Write packets go func() { defer KILL() for { select { case <-ctx.killswitch: return case xaxa := <-ctx.write_ch: lel := "data" if xaxa.flag == icom_ignore { lel = "junk" } else if xaxa.flag == icom_open { lel = "open" } else if xaxa.flag == icom_close { lel = "clos" } else if xaxa.flag == icom_more { lel = "more" } kilog.FineDebug("[ICOM] -> %v\t%v\t%v:%v", do_junk, xaxa.connid, lel, len(xaxa.body)) buffer := new(bytes.Buffer) desired_size := prob_dist.Draw() prob_dist.Juggle() xaxa.WriteTo(buffer) if desired_size > len(xaxa.body) && do_junk { excess := desired_size - len(xaxa.body) padd := icom_msg{icom_ignore, 0, make([]byte, excess)} padd.WriteTo(buffer) } if xaxa.flag == icom_data && do_junk { // Draw a waiting period wsecs := rand.ExpFloat64() * 3 wms := int64(wsecs * 1000) // Spin off a goroutine to do this! go func() { time.Sleep(time.Millisecond * time.Duration(wms)) select { case junk_chan <- true: default: } }() } _, err := ctx.underlying.Write(buffer.Bytes()) if err != nil { return } } } }() // Keepalive pakkets if do_junk { go func() { for { select { case <-ctx.killswitch: return case <-time.After(time.Second * time.Duration(rand.Int()%5)): select { case <-ctx.killswitch: return case ctx.write_ch <- icom_msg{icom_ignore, 0, make([]byte, 0)}: } } } }() } // Client side. Writes stuff. if !is_server { go func() { defer KILL() for { // Accepts clients incoming, err := ctx.our_srv.Accept() if err != nil { kilog.Debug("** icom_ctx dead @ client accept **") return } // Find a connid <-stable_lock connid := 0 startidx := rand.Int() % 65536 // DEBUG!!! //startidx = 0 for i := 0; i < 65536; i++ { if socket_table[(i+startidx)%65536] == nil { connid = (i + startidx) % 65536 break } } ctx.write_ch <- icom_msg{icom_open, connid, make([]byte, 0)} xaxa := make(chan icom_msg, PAUSELIM) socket_table[connid] = xaxa stable_lock <- true go func() { if !do_junk { kilog.Debug("ICOM: Opened connid %d", connid) } icom_tunnel(ctx, KILL, incoming, connid, xaxa, do_junk, PAUSELIM) <-stable_lock socket_table[connid] = nil stable_lock <- true if !do_junk { kilog.Debug("ICOM: Closed connid %d", connid) } }() } }() } // Reading link for { var justread icom_msg err := justread.ReadFrom(ctx.underlying) if err != nil { kilog.Debug("** icom_ctx dead @ body ** due to %s", err.Error()) return } xaxa := justread lel := "data" if xaxa.flag == icom_ignore { lel = "junk" } else if xaxa.flag == icom_open { lel = "open" } else if xaxa.flag == icom_close { lel = "clos" } else if xaxa.flag == icom_more { lel = "more" } kilog.FineDebug("[ICOM] <- %v\t%v\t%v:%v", do_junk, xaxa.connid, lel, len(xaxa.body)) // Now work with the packet if justread.flag == icom_ignore { continue } if justread.flag == icom_open && is_server { if !do_junk { kilog.Debug("ICOM: Accepted connid %d", justread.connid) } // Open a connection! The caller of accept will unblock this call. conn, err := VSConnect(ctx.our_srv) if err != nil { return } xaxa := make(chan icom_msg, PAUSELIM) <-stable_lock socket_table[justread.connid] = xaxa stable_lock <- true go func() { kilog.Debug("ICOM: Began processing connid %d", justread.connid) // Tunnel the connection icom_tunnel(ctx, KILL, conn, justread.connid, xaxa, do_junk, PAUSELIM) if !do_junk { kilog.Debug("ICOM: Closed connid %d", justread.connid) } <-stable_lock socket_table[justread.connid] = nil stable_lock <- true }() } else if justread.flag == icom_data || justread.flag == icom_more || justread.flag == icom_close { <-stable_lock if socket_table[justread.connid] == nil { stable_lock <- true continue } ch := socket_table[justread.connid] stable_lock <- true // Forward the data to the socket select { case ch <- justread: case <-ctx.killswitch: return default: } } else { kilog.Debug("** icom_ctx dead ** due to invalid packet") return } } }